diff --git a/docs/affiliates.md b/docs/affiliates.md
index bcc099c79e0..1dc4be5f9a6 100644
--- a/docs/affiliates.md
+++ b/docs/affiliates.md
@@ -1,95 +1,77 @@
# ShapeShift Affiliate Program
-Earn revenue share on swaps executed through your integration.
+Earn revenue share on swaps executed through your integration — whether you embed the
+[Swap Widget](../packages/swap-widget/README.md) or call the
+[Public API](../packages/public-api/docs/introduction.md) directly.
+
+Attribution is driven by a **partner code**: a short identifier registered to an EVM wallet address.
+Pass it to the widget or the API, and ShapeShift attributes each swap to your affiliate account and
+applies your configured fee automatically.
## Quick Start
-### 1. Using the Swap Widget
+### 1. Get a partner code
+
+Register at the [Affiliate Dashboard](https://dashboard.affiliate.shapeshift.com/): connect your wallet, sign
+in (a wallet signature — no gas), then choose your partner code and fee. The code is mapped to your
+wallet, and you'll use it everywhere below.
+
+### 2. Using the Swap Widget
+
+Pass your `partnerCode` prop. The widget renders once Reown AppKit is initialized: either pass
+`walletConnectProjectId` and the widget initializes AppKit for you (shown below), or initialize AppKit
+yourself in the host app and the widget reads the shared instance — see the
+[widget README](../packages/swap-widget/README.md).
```tsx
import { SwapWidget } from '@shapeshiftoss/swap-widget'
```
-### 2. Using the Public API
-
-Include these headers in your API requests:
+### 3. Using the Public API
-```bash
-curl https://api.shapeshift.com/v1/swap/rates \
- -H "X-Affiliate-Address: 0xYourWalletAddress" \
- -H "Content-Type: application/json" \
- -d '{"sellAssetId": "...", "buyAssetId": "...", "sellAmountCryptoBaseUnit": "..."}'
-```
-
-Or use a partner code:
+Send the `X-Partner-Code` header on the swap endpoints (`/v1/swap/rates`, `/v1/swap/quote`,
+`/v1/swap/status`). The API attributes the swap to your affiliate account and applies your configured fee.
```bash
-curl https://api.shapeshift.com/v1/swap/rates \
- -H "X-Partner-Code: yourcode" \
- -H "Content-Type: application/json" \
- -d '...'
+curl "https://api.shapeshift.com/v1/swap/rates?sellAssetId=eip155:1/slip44:60&buyAssetId=eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&sellAmountCryptoBaseUnit=1000000000000000000" \
+ -H "X-Partner-Code: your-partner-code"
```
-## Headers
-
-| Header | Description |
-|--------|-------------|
-| `X-Affiliate-Address` | Your EVM wallet address (0x...) |
-| `X-Partner-Code` | Your partner code (if registered) |
-| `X-Affiliate-Bps` | Override BPS (optional, 0-1000) |
-
-## Registering as an Affiliate
+## The `X-Partner-Code` header
-1. Go to the [Affiliate Dashboard](https://affiliate.shapeshift.com)
-2. Connect your wallet
-3. Your address is automatically registered with default BPS
-4. Optionally claim a partner code
+| Header | Description |
+| ---------------- | ------------------------------------------------------------------------ |
+| `X-Partner-Code` | Your registered partner code. Attributes the swap to your affiliate account and applies your fee. |
-### Partner Codes
+The partner code is the attribution mechanism: it maps to the affiliate parameters (payout address
+and fee) configured for your account, so there's no separate address or bps header to send. Requests
+without a partner code still succeed, but the swap is unattributed and uses the default fee.
-Partner codes are short identifiers (3-32 alphanumeric characters) that map to your wallet. Benefits:
+## Fees
-- Easier to share than a wallet address
-- Can be used in place of `X-Affiliate-Address`
-- One code per affiliate
+Fees are expressed in **basis points (bps)** — 1 bps = 0.01%, so 60 bps = 0.6%. The fee that applies
+to swaps attributed to your partner code is configured at registration; swaps with no partner code
+use ShapeShift's default fee.
-Reserved codes: `shapeshift`, `ss`, `admin`, `api`, `test`, `demo`
+The API surfaces the fee breakdown for each rate/quote via the `affiliateBps` (total) and
+`shapeshiftBps` fields, plus `partnerBps` when the swap is attributed to a partner code — these
+reflect the fee that will actually be applied, so read them per quote rather than assuming a fixed rate.
-## Fee Structure
+## Revenue Attribution & Reporting
-| BPS | Percentage | Description |
-|-----|------------|-------------|
-| 10 | 0.1% | Minimum (API base) |
-| 60 | 0.6% | Default |
-| 100 | 1.0% | Example custom |
-
-- **BPS** = Basis Points (1 BPS = 0.01%)
-- Fees are taken from the sell amount
-- Related asset swaps (e.g., ETH → WETH) have 0% fee
-
-## Revenue Attribution
-
-Every swap executed through your integration is tracked:
-
-- Your affiliate address is recorded
-- Volume and fees are calculated
-- Stats available in the affiliate dashboard
-
-### Viewing Your Stats
+Every swap carrying your partner code is attributed to you. Review your activity in the dashboard, or
+query the API by partner code:
```bash
-# Get your stats
-curl "https://api.shapeshift.com/v1/affiliate/stats?address=0xYour..."
+# Aggregate stats (optional startDate / endDate)
+curl "https://api.shapeshift.com/v1/affiliate/stats?partnerCode=your-partner-code"
-# Response
+# Example response
{
"totalSwaps": 1234,
"totalVolumeUsd": "1234567.89",
@@ -97,25 +79,24 @@ curl "https://api.shapeshift.com/v1/affiliate/stats?address=0xYour..."
}
```
-## API Endpoints
-
-### Public Endpoints
-
-| Method | Endpoint | Description |
-|--------|----------|-------------|
-| GET | `/v1/affiliate/stats?address=...` | Get swap stats |
-| GET | `/v1/partner/:code` | Resolve partner code |
+```bash
+# Paginated swap history (optional startDate / endDate / limit / cursor)
+curl "https://api.shapeshift.com/v1/affiliate/swaps?partnerCode=your-partner-code"
+```
-### Authenticated Endpoints (requires wallet signature)
+## Affiliate API Endpoints
-| Method | Endpoint | Description |
-|--------|----------|-------------|
-| POST | `/v1/affiliate` | Register as affiliate |
-| PATCH | `/v1/affiliate/:address` | Update settings |
-| POST | `/v1/affiliate/claim-code` | Claim partner code |
+| Method | Endpoint | Auth | Description |
+| ------ | ----------------------------------------- | --------------- | ---------------------------------------- |
+| GET | `/v1/affiliate/stats?partnerCode=...` | none | Aggregate swap stats for a partner code |
+| GET | `/v1/affiliate/swaps?partnerCode=...` | none | Paginated swap history for a partner code |
+| GET | `/v1/partner/{code}` | none | Resolve a partner code to its attribution details (address, bps split) |
+| GET | `/v1/affiliate/{address}` | none | Look up an affiliate by wallet address |
+| POST | `/v1/affiliate` | wallet (SIWE) | Register as an affiliate |
+| PATCH | `/v1/affiliate/{address}` | wallet (SIWE) | Update affiliate settings |
-## Support
+Wallet-authenticated endpoints use Sign-In With Ethereum (`POST /v1/auth/siwe/nonce` →
+`POST /v1/auth/siwe/verify`). Most partners never call these directly — the
+[Affiliate Dashboard](https://dashboard.affiliate.shapeshift.com/) handles registration for you.
-- **Dashboard**: https://affiliate.shapeshift.com
-- **Discord**: https://discord.gg/shapeshift
-- **Email**: affiliates@shapeshift.com
+See the full request/response schemas in the [Public API reference](https://api.shapeshift.com/docs).
diff --git a/docs/architecture/affiliate-data-model.md b/docs/architecture/affiliate-data-model.md
deleted file mode 100644
index ab3a796e11c..00000000000
--- a/docs/architecture/affiliate-data-model.md
+++ /dev/null
@@ -1,310 +0,0 @@
-# Affiliate System Data Model
-
-## Overview
-
-This document defines the data model for the affiliate system, to be implemented in `shapeshift/microservices`.
-
-## Prisma Schema
-
-```prisma
-// Add to prisma/schema.prisma in microservices
-
-model Affiliate {
- id String @id @default(uuid())
- walletAddress String @unique @map("wallet_address") @db.VarChar(42)
- partnerCode String? @unique @map("partner_code") @db.VarChar(32)
- bps Int @default(60)
- isActive Boolean @default(true) @map("is_active")
- createdAt DateTime @default(now()) @map("created_at")
- updatedAt DateTime @updatedAt @map("updated_at")
-
- // Relations
- swaps Swap[] @relation("AffiliateSwaps")
-
- @@map("affiliates")
- @@index([partnerCode])
- @@index([isActive])
-}
-
-// Extend existing Swap model
-model Swap {
- // ... existing fields ...
-
- // New affiliate fields
- affiliateAddress String? @map("affiliate_address") @db.VarChar(42)
- affiliateBps Int? @map("affiliate_bps")
- affiliateFeeUsd Decimal? @map("affiliate_fee_usd") @db.Decimal(18, 8)
-
- // Relation to affiliate (optional, for registered affiliates)
- affiliate Affiliate? @relation("AffiliateSwaps", fields: [affiliateAddress], references: [walletAddress])
-
- @@index([affiliateAddress])
-}
-
-// Partner codes can map to multiple affiliates (organizations)
-model PartnerCodeMember {
- id String @id @default(uuid())
- partnerCode String @map("partner_code") @db.VarChar(32)
- walletAddress String @map("wallet_address") @db.VarChar(42)
- role String @default("member") @db.VarChar(16) // "owner" | "admin" | "member"
- createdAt DateTime @default(now()) @map("created_at")
-
- @@unique([partnerCode, walletAddress])
- @@map("partner_code_members")
- @@index([partnerCode])
- @@index([walletAddress])
-}
-```
-
-## DTOs (Data Transfer Objects)
-
-```typescript
-// packages/shared-types/src/affiliate.ts
-
-export interface AffiliateDto {
- id: string
- walletAddress: string
- partnerCode: string | null
- bps: number
- isActive: boolean
- createdAt: string
- updatedAt: string
-}
-
-export interface CreateAffiliateDto {
- walletAddress: string
- partnerCode?: string
- bps?: number
-}
-
-export interface UpdateAffiliateDto {
- bps?: number
- isActive?: boolean
-}
-
-export interface AffiliateStatsDto {
- totalSwaps: number
- totalVolumeUsd: string
- totalFeesEarnedUsd: string
- periodStart?: string
- periodEnd?: string
-}
-
-export interface AffiliateSwapDto {
- swapId: string
- sellAsset: string
- buyAsset: string
- sellAmountUsd: string
- buyAmountUsd: string
- affiliateFeeUsd: string
- status: string
- createdAt: string
- txHash?: string
-}
-
-export interface ClaimPartnerCodeDto {
- partnerCode: string
-}
-
-export interface PartnerCodeResolutionDto {
- partnerCode: string
- affiliateAddress: string
- bps: number
-}
-```
-
-## API Contracts
-
-### GET /v1/affiliate/:address
-
-Get affiliate configuration by wallet address.
-
-**Response:**
-```json
-{
- "id": "uuid",
- "walletAddress": "0x...",
- "partnerCode": "vultisig",
- "bps": 100,
- "isActive": true,
- "createdAt": "2026-03-12T00:00:00Z",
- "updatedAt": "2026-03-12T00:00:00Z"
-}
-```
-
-**404 Response:** Affiliate not registered (use default BPS)
-
-### POST /v1/affiliate
-
-Register as affiliate. Requires SIWE authentication.
-
-**Headers:**
-```
-Authorization: Bearer
-```
-
-**Request:**
-```json
-{
- "walletAddress": "0x...",
- "partnerCode": "mycode",
- "bps": 60
-}
-```
-
-**Response:** Created affiliate object
-
-### PATCH /v1/affiliate/:address
-
-Update affiliate settings. Requires SIWE auth matching address.
-
-**Request:**
-```json
-{
- "bps": 100
-}
-```
-
-### GET /v1/affiliate/:address/stats
-
-Get aggregate swap statistics.
-
-**Query params:**
-- `startDate` (optional): ISO date
-- `endDate` (optional): ISO date
-
-**Response:**
-```json
-{
- "totalSwaps": 1234,
- "totalVolumeUsd": "1234567.89",
- "totalFeesEarnedUsd": "7407.41"
-}
-```
-
-### GET /v1/affiliate/:address/swaps
-
-Get paginated swap history.
-
-**Query params:**
-- `page` (default: 1)
-- `limit` (default: 50, max: 100)
-- `startDate` (optional)
-- `endDate` (optional)
-
-**Response:**
-```json
-{
- "swaps": [
- {
- "swapId": "uuid",
- "sellAsset": "ETH",
- "buyAsset": "BTC",
- "sellAmountUsd": "1000.00",
- "buyAmountUsd": "995.00",
- "affiliateFeeUsd": "6.00",
- "status": "completed",
- "createdAt": "2026-03-12T00:00:00Z",
- "txHash": "0x..."
- }
- ],
- "pagination": {
- "page": 1,
- "limit": 50,
- "total": 1234,
- "pages": 25
- }
-}
-```
-
-### GET /v1/partner/:code
-
-Resolve partner code to affiliate configuration.
-
-**Response:**
-```json
-{
- "partnerCode": "vultisig",
- "affiliateAddress": "0x...",
- "bps": 100
-}
-```
-
-### POST /v1/affiliate/claim-code
-
-Claim a partner code. Requires SIWE auth.
-
-**Request:**
-```json
-{
- "partnerCode": "mycode"
-}
-```
-
-**Validation:**
-- Code must be 3-32 alphanumeric characters
-- Code must not be taken
-- One code per affiliate (can transfer)
-
-## Migration
-
-```sql
--- Migration: add_affiliate_tables
-
-CREATE TABLE affiliates (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- wallet_address VARCHAR(42) NOT NULL UNIQUE,
- partner_code VARCHAR(32) UNIQUE,
- bps INTEGER NOT NULL DEFAULT 60,
- is_active BOOLEAN NOT NULL DEFAULT true,
- created_at TIMESTAMP NOT NULL DEFAULT NOW(),
- updated_at TIMESTAMP NOT NULL DEFAULT NOW()
-);
-
-CREATE INDEX idx_affiliates_partner_code ON affiliates(partner_code);
-CREATE INDEX idx_affiliates_is_active ON affiliates(is_active);
-
--- Add columns to existing swaps table
-ALTER TABLE swaps ADD COLUMN affiliate_address VARCHAR(42);
-ALTER TABLE swaps ADD COLUMN affiliate_bps INTEGER;
-ALTER TABLE swaps ADD COLUMN affiliate_fee_usd DECIMAL(18, 8);
-
-CREATE INDEX idx_swaps_affiliate_address ON swaps(affiliate_address);
-
--- Partner code members (for organizations)
-CREATE TABLE partner_code_members (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- partner_code VARCHAR(32) NOT NULL,
- wallet_address VARCHAR(42) NOT NULL,
- role VARCHAR(16) NOT NULL DEFAULT 'member',
- created_at TIMESTAMP NOT NULL DEFAULT NOW(),
- UNIQUE(partner_code, wallet_address)
-);
-
-CREATE INDEX idx_partner_code_members_code ON partner_code_members(partner_code);
-CREATE INDEX idx_partner_code_members_wallet ON partner_code_members(wallet_address);
-```
-
-## Validation Rules
-
-### Wallet Address
-- Must be valid EVM address (0x + 40 hex chars)
-- Checksummed for storage
-
-### Partner Code
-- 3-32 characters
-- Alphanumeric + hyphens only
-- Case-insensitive (stored lowercase)
-- Reserved codes: `shapeshift`, `ss`, `admin`, `api`, `test`
-
-### BPS
-- Range: 0-1000 (0% to 10%)
-- Default: 60 (0.6%)
-- Only modifiable by affiliate owner or admin
-
-## Security Considerations
-
-1. **SIWE Authentication**: All write operations require signed message proving wallet ownership
-2. **Rate Limiting**: Stats endpoints rate-limited per address
-3. **Address Validation**: Strict EVM address validation
-4. **Partner Code Squatting**: Consider verification for branded codes
diff --git a/docs/architecture/affiliate-system.md b/docs/architecture/affiliate-system.md
index c1586ecc427..80f0c21b945 100644
--- a/docs/architecture/affiliate-system.md
+++ b/docs/architecture/affiliate-system.md
@@ -2,292 +2,171 @@
## Overview
-The ShapeShift affiliate system allows partners to earn revenue share on swaps executed through their integration. This document describes the current state and proposed improvements.
+The ShapeShift affiliate system lets partners earn revenue share on swaps executed through their
+integration (Swap Widget or Public API). Attribution is keyed on a **partner code** registered to an
+EVM wallet address (the partner's attribution/settlement address). This document describes the
+implemented architecture.
-## Current State
+> **Scope:** This repo (`shapeshift/web`) contains the widget, the Public API, and the swapper
+> packages. Affiliate **persistence** (the affiliate registry, partner-code mapping, and swap
+> records) lives in the separate `shapeshift/microservices` repo (the swap-service); that repo is
+> authoritative for the database schema.
-### Data Flow Diagram
+## Data Flow
```mermaid
flowchart TB
- subgraph "Web App"
- WA[User initiates swap]
- WA --> GBps[getAffiliateBps]
- GBps --> |"DEFAULT_FEE_BPS = 60"|SwapperApi
- SwapperApi --> SwapperPkg[packages/swapper]
+ subgraph "Widget / Partner UI"
+ WG[SwapWidget or partner client]
+ WG --> |partnerCode prop / X-Partner-Code header|PublicApi
end
- subgraph "Widget"
- WG[SwapWidget Component]
- WG --> |affiliateAddress prop|ApiClient
- ApiClient --> |X-Affiliate-Address header|PublicApi
+ subgraph "Public API (packages/public-api)"
+ PublicApi[swap endpoints]
+ PublicApi --> |resolvePartnerCode middleware|Resolve{X-Partner-Code present?}
+ Resolve --> |yes: resolve via swap-service|AffiliateInfo[affiliateInfo: partnerAddress, partnerBps, shapeshiftBps, affiliateBps]
+ Resolve --> |no: DEFAULT_AFFILIATE_BPS|AffiliateInfo
+ AffiliateInfo --> |total affiliateBps only|SwapperPkg[packages/swapper]
end
- subgraph "Public API"
- PublicApi[packages/public-api]
- PublicApi --> |affiliateInfo middleware|SwapperPkg
+ subgraph "Swapper Packages (packages/swapper)"
+ SwapperPkg --> |total applied as the ShapeShift affiliate fee|Swappers[Individual swappers]
end
- subgraph "Swapper Packages"
- SwapperPkg --> |affiliateBps param|Swappers
- Swappers[Individual Swappers]
- Swappers --> |THORChain: ss affiliate|THORChain
- Swappers --> |Jupiter: REFER4Z...|Jupiter
- Swappers --> |CoW: appData|CoWProtocol
- Swappers --> |etc.|OtherDEXs
- end
-
- subgraph "Microservices"
+ subgraph "Microservices (shapeshift/microservices)"
MS[swap-service]
- MS --> |stores referralCode only|DB[(PostgreSQL)]
+ MS --> |partner mapping, swap records, settlement|DB[(PostgreSQL)]
end
- SwapperPkg --> |swap executed|MS
+ PublicApi --> |register swap: partnerAddress + code + bps split, for off-chain settlement|MS
+ PublicApi --> |resolve partner code|MS
```
-### Component Details
+## Attribution model
-#### 1. Web App (`src/lib/fees/`)
+The key distinction: the swap charges a **single on-chain affiliate fee** — the **total**
+`affiliateBps` — collected by ShapeShift's own affiliate accounts via each protocol's mechanism. The
+`partnerBps` / `shapeshiftBps` breakdown and the `partnerAddress` are **attribution metadata**, not
+separate on-chain fees:
-**Files:**
-- `src/lib/fees/constant.ts` - Defines `DEFAULT_FEE_BPS = '60'` (0.6%)
-- `src/lib/fees/utils.ts` - `getAffiliateBps()` function
-
-**Logic:**
-```typescript
-// src/lib/fees/constant.ts
-export const DEFAULT_FEE_BPS = '60' // basis points (0.6%)
-
-// src/lib/fees/utils.ts
-export const getAffiliateBps = (sellAsset: Asset, buyAsset: Asset): string => {
- // Related asset swaps (e.g., ETH → WETH) have 0 fee
- return isRelatedAssetSwap(sellAsset, buyAsset) ? '0' : DEFAULT_FEE_BPS
-}
-```
+- Only the **total** `affiliateBps` is passed to the swapper (`req.affiliateInfo.affiliateBps`). The
+ `partnerAddress` and the bps split are **never** sent to the swapper, so the partner is **not** paid
+ on-chain.
+- The split + partner address travel to the swap-service when the swap is registered, where they back
+ reporting (`/v1/affiliate/stats`, `/v1/affiliate/swaps`) and **off-chain revenue-share settlement**
+ to the partner.
+- `affiliateBps = partnerBps + shapeshiftBps`. With no resolvable partner code the swap is
+ unattributed: `partnerBps` is absent and `affiliateBps = shapeshiftBps = DEFAULT_AFFILIATE_BPS`.
-**Usage:**
-- Called in `src/components/MultiHopTrade/hooks/useGetTradeRateInput.ts`
-- Passed to `swapperApi` endpoints in `src/state/apis/swapper/swapperApi.ts`
+## Components
-#### 2. Widget (`packages/swap-widget/`)
+### 1. Widget (`packages/swap-widget/`)
-**Props:**
-```typescript
-// packages/swap-widget/src/types/index.ts
-export type SwapWidgetProps = {
- affiliateAddress?: string // EVM address for affiliate
- // ... other props
-}
-```
-
-**API Client:**
-```typescript
-// packages/swap-widget/src/api/client.ts
-// Passes affiliateAddress to API requests
-```
+The widget takes a single `partnerCode` prop and forwards it to the Public API as the
+`X-Partner-Code` header (via its internal API client). There is no affiliate-address or bps prop —
+fee configuration is resolved server-side from the partner code. See the
+[widget README](../../packages/swap-widget/README.md).
-**Current Limitation:** Widget only passes address, cannot configure custom BPS.
+### 2. Public API (`packages/public-api/`)
-#### 3. Public API (`packages/public-api/`)
+**Partner-code middleware** — `resolvePartnerCode` runs on the swap endpoints
+(`/v1/swap/rates`, `/v1/swap/quote`, `/v1/swap/status`):
-**Middleware:**
```typescript
// packages/public-api/src/middleware/auth.ts
-export const affiliateAddress = (req, res, next) => {
- const address = req.header('X-Affiliate-Address')
- if (address && EVM_ADDRESS_REGEX.test(address)) {
- req.affiliateInfo = { affiliateAddress: address }
+export const resolvePartnerCode = async (req, _res, next) => {
+ const partnerCode = req.header('X-Partner-Code')
+
+ if (partnerCode) {
+ const resolved = await resolvePartnerCodeFromService(partnerCode) // -> swap-service /v1/partner/:code
+ if (resolved) {
+ req.affiliateInfo = {
+ partnerAddress: resolved.partnerAddress,
+ partnerBps: resolved.partnerBps,
+ shapeshiftBps: resolved.shapeshiftBps,
+ affiliateBps: String(Number(resolved.partnerBps) + Number(resolved.shapeshiftBps)),
+ partnerCode,
+ }
+ return next()
+ }
}
- next()
-}
-```
-
-**Response Types:**
-```typescript
-// packages/public-api/src/types.ts
-export type ApiRate = {
- // ...
- affiliateBps: string // Returned in responses
-}
-export type RatesResponse = {
- rates: ApiRate[]
- affiliateAddress?: string // Echo back the affiliate
+ // No (or unresolvable) partner code — unattributed swap uses the default fee
+ req.affiliateInfo = {
+ shapeshiftBps: env.DEFAULT_AFFILIATE_BPS,
+ affiliateBps: env.DEFAULT_AFFILIATE_BPS,
+ }
+ next()
}
```
-**Current Limitation:** Accepts address but doesn't look up affiliate-specific BPS.
-
-#### 4. Swapper Packages (`packages/swapper/`)
+**Bps fields** — rate, quote, and status responses carry the fee breakdown:
-Each swapper handles affiliate fees differently:
-
-| Swapper | Affiliate Mechanism | Config Location |
-|---------|---------------------|-----------------|
-| THORChain | `affiliate=ss` in memo | `THORCHAIN_AFFILIATE_NAME` |
-| Mayachain | `affiliate=ssmaya` in memo | `MAYACHAIN_AFFILIATE_NAME` |
-| Jupiter | `REFER4Z...` contract | `JUPITER_AFFILIATE_CONTRACT_ADDRESS` |
-| CoW Protocol | `appData` JSON | Included in order |
-| Butter | `shapeshift` affiliate | `BUTTERSWAP_AFFILIATE` |
-
-**BPS Flow:**
```typescript
-// packages/swapper/src/swappers/*/getTradeQuote.ts
-const quote = await getQuote({
- // ...
- affiliateBps, // Passed through to DEX APIs
-})
+// packages/public-api/src/types.ts (BpsFields)
+affiliateBps: string // total fee charged on-chain, in bps
+partnerBps?: string // partner's attributed share (present when attributed)
+shapeshiftBps: string // ShapeShift's attributed share
```
-#### 5. Microservices (`shapeshift/microservices`)
+These reflect the [attribution model](#attribution-model) above: `affiliateBps` is the fee actually
+charged; `partnerBps` / `shapeshiftBps` describe how it is attributed for settlement.
-**swap-service:**
-```typescript
-// apps/swap-service/src/swaps/swaps.service.ts
-async createSwap(data: CreateSwapDto) {
- // Stores referralCode (from user-service)
- // Does NOT store affiliate address or BPS
- const swap = await this.prisma.swap.create({
- data: {
- // ...
- referralCode, // From user-service lookup
- // Missing: affiliateAddress, affiliateBps
- },
- })
-}
-```
-
-**Current Limitation:** No affiliate tracking or revenue attribution.
-
-#### 6. Affiliate Dashboard (`packages/affiliate-dashboard/`)
-
-**Current Features:**
-- Address input (no auth)
-- Stats display: swaps, volume, fees
-- Period filtering
-- Fetches from `/v1/affiliate/stats`
-
-**Endpoint Required:** `/v1/affiliate/stats` (needs to be implemented in microservices)
-
----
-
-## Gaps Identified
-
-### 1. No Affiliate BPS Storage
-- Widget/API affiliates get whatever BPS is hardcoded
-- No way to configure per-affiliate BPS
-- No database table for affiliate configuration
-
-### 2. No Swap Attribution
-- Swaps are not tagged with affiliate address
-- Cannot query "swaps through affiliate X"
-- Revenue attribution impossible
-
-### 3. No Partner Code System
-- Cannot use friendly codes like "vultisig" or "venice"
-- Must use raw wallet addresses
-
-### 4. No Authentication
-- Affiliate dashboard has no wallet auth
-- Anyone can view any affiliate's stats
-- Cannot update own BPS without backend
+**Swap registration** — when a swap is bound to a tx (first `GET /v1/swap/status` with a `txHash`),
+the Public API registers it with the swap-service, forwarding the `partnerCode`, `partnerAddress`,
+and the `partnerBps` / `shapeshiftBps` / `affiliateBps` breakdown so attribution can be persisted and
+settled (see `routes/status/utils.ts`).
-### 5. Inconsistent BPS Across Surfaces
-- Web app: 60 BPS (hardcoded)
-- Widget: Uses API default (should be configurable)
-- Public API: No lookup, uses caller's requested BPS
+### 3. Swapper Packages (`packages/swapper/`)
----
+Only the total `affiliateBps` (partner + ShapeShift) reaches the swapper. Each swapper applies it as
+**ShapeShift's own** affiliate fee using that protocol's mechanism; the partner address is not
+involved at this layer.
-## Proposed Solution
+### 4. Microservices (`shapeshift/microservices`)
-See [Affiliate System Alignment Spike](../beads/web-bqz.md) for full implementation plan.
+The swap-service owns affiliate persistence: the partner-code → affiliate mapping resolved by
+`resolvePartnerCodeFromService`, and the swap records that back the stats/swaps endpoints. The
+Public API talks to it over HTTP with an API key (`SWAP_SERVICE_API_KEY`).
-### High-Level Architecture
+### 5. Affiliate Dashboard (`packages/affiliate-dashboard/`)
-```mermaid
-flowchart TB
- subgraph "Affiliate Dashboard"
- AD[packages/affiliate-dashboard]
- AD --> |Reown wallet auth|Arbitrum
- AD --> |SIWE sign|AffiliateAPI
- end
+Partner-facing UI for registering an affiliate (wallet + SIWE sign-in), obtaining a partner code, and
+viewing stats. It is the recommended way for partners to register rather than calling the
+wallet-authenticated endpoints directly.
- subgraph "Microservices"
- AffiliateAPI[/v1/affiliate/*]
- AffiliateAPI --> AffiliateTable[(affiliates table)]
- AffiliateAPI --> SwapTable[(swaps table)]
-
- AffiliateTable --> |walletAddress|Lookup
- AffiliateTable --> |partnerCode|Lookup
- AffiliateTable --> |bps|Lookup
- end
+## Affiliate API Endpoints
- subgraph "Public API / Widget"
- PublicApi2[X-Affiliate-Address]
- PublicApi2 --> |lookup BPS|AffiliateAPI
- PublicApi2 --> |apply correct BPS|SwapperPkg2[Swapper]
- end
+Implemented in `packages/public-api` (see the [API reference](https://api.shapeshift.com/docs) for
+full schemas):
- subgraph "Swap Execution"
- SwapperPkg2 --> |tag with affiliateAddress|SwapService
- SwapService --> SwapTable
- end
```
-
-### Database Schema (Proposed)
-
-```sql
--- affiliates table
-CREATE TABLE affiliates (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- wallet_address VARCHAR(42) NOT NULL UNIQUE,
- partner_code VARCHAR(32) UNIQUE,
- bps INTEGER NOT NULL DEFAULT 60,
- is_active BOOLEAN NOT NULL DEFAULT true,
- created_at TIMESTAMP NOT NULL DEFAULT NOW(),
- updated_at TIMESTAMP NOT NULL DEFAULT NOW()
-);
-
--- Add to swaps table
-ALTER TABLE swaps ADD COLUMN affiliate_address VARCHAR(42);
-ALTER TABLE swaps ADD COLUMN affiliate_bps INTEGER;
+GET /v1/affiliate/stats?partnerCode=... # aggregate stats (optional startDate/endDate)
+GET /v1/affiliate/swaps?partnerCode=... # paginated swap history (optional startDate/endDate/limit/cursor)
+GET /v1/partner/{code} # resolve a partner code -> { partnerAddress, partnerBps, shapeshiftBps }
+GET /v1/affiliate/{address} # look up an affiliate by wallet address
+POST /v1/affiliate # register (SIWE-authenticated)
+PATCH /v1/affiliate/{address} # update settings (SIWE-authenticated)
+POST /v1/auth/siwe/nonce # SIWE: request nonce
+POST /v1/auth/siwe/verify # SIWE: verify signature
```
-### API Endpoints (Proposed)
-
-```
-GET /v1/affiliate/:address - Get affiliate config
-POST /v1/affiliate - Register (with SIWE auth)
-PATCH /v1/affiliate/:address - Update BPS (with SIWE auth)
-GET /v1/affiliate/:address/stats - Get swap stats
-GET /v1/affiliate/:address/swaps - Get swap history
-POST /v1/affiliate/claim-code - Claim partner code (with SIWE auth)
-GET /v1/partner/:code - Resolve partner code to affiliate
-```
+## Related Files
----
+### Public API
-## Related Files
+- `packages/public-api/src/middleware/auth.ts` — partner-code resolution
+- `packages/public-api/src/types.ts` — `AffiliateInfo`, `BpsFields`
+- `packages/public-api/src/routes/affiliate/` — stats, swaps, get/create/update affiliate
+- `packages/public-api/src/routes/partner/getPartner.ts` — partner-code resolution endpoint
+- `packages/public-api/src/routes/status/utils.ts` — swap registration with `partnerCode`
-### Web App
-- `src/lib/fees/constant.ts`
-- `src/lib/fees/utils.ts`
-- `src/state/apis/swapper/swapperApi.ts`
-- `src/components/MultiHopTrade/hooks/useGetTradeRateInput.ts`
+### Widget & Dashboard
-### Packages
-- `packages/public-api/src/middleware/auth.ts`
-- `packages/public-api/src/types.ts`
-- `packages/swap-widget/src/types/index.ts`
+- `packages/swap-widget/src/types/index.ts` — `SwapWidgetProps.partnerCode`
+- `packages/swap-widget/src/api/client.ts` — sends `X-Partner-Code`
- `packages/affiliate-dashboard/src/`
-### Swapper Affiliate Constants
-- `packages/swapper/src/swappers/ThorchainSwapper/constants.ts`
-- `packages/swapper/src/swappers/MayachainSwapper/constants.ts`
-- `packages/swapper/src/swappers/JupiterSwapper/utils/constants.ts`
-- `packages/swapper/src/swappers/ButterSwap/utils/constants.ts`
+### Microservices (separate repo: `shapeshift/microservices`)
-### Microservices
-- `apps/swap-service/src/swaps/swaps.service.ts`
-- `apps/swap-service/src/swaps/swaps.controller.ts`
+- swap-service — affiliate registry, partner-code mapping, swap records
diff --git a/packages/affiliate-dashboard/src/App.tsx b/packages/affiliate-dashboard/src/App.tsx
index bf8d1a15da1..b197d615171 100644
--- a/packages/affiliate-dashboard/src/App.tsx
+++ b/packages/affiliate-dashboard/src/App.tsx
@@ -36,6 +36,7 @@ export const App = (): React.JSX.Element => {
const affiliateAddress = isConnected && address ? address : ''
const configQuery = useAffiliateConfig(affiliateAddress)
+ const partnerCode = configQuery.data?.partnerCode ?? ''
const periods = useMemo(
() => generatePeriods(configQuery.data?.createdAt),
@@ -44,8 +45,8 @@ export const App = (): React.JSX.Element => {
const currentPeriod = periods.find(p => p.key === selectedKey) ?? periods[0]
- const statsQuery = useAffiliateStats(affiliateAddress, currentPeriod)
- const swapsQuery = useAffiliateSwaps(affiliateAddress, currentPeriod)
+ const statsQuery = useAffiliateStats(partnerCode, currentPeriod)
+ const swapsQuery = useAffiliateSwaps(partnerCode, currentPeriod)
const actions = useAffiliateActions({ affiliateAddress, authHeaders })
const swaps = useMemo(
diff --git a/packages/affiliate-dashboard/src/hooks/useAffiliateConfig.ts b/packages/affiliate-dashboard/src/hooks/useAffiliateConfig.ts
index a6fdcc345d0..77695cffee7 100644
--- a/packages/affiliate-dashboard/src/hooks/useAffiliateConfig.ts
+++ b/packages/affiliate-dashboard/src/hooks/useAffiliateConfig.ts
@@ -9,7 +9,7 @@ const AffiliateConfigSchema = z.object({
id: z.string(),
walletAddress: z.string(),
receiveAddress: z.string().nullable(),
- partnerCode: z.string().nullable(),
+ partnerCode: z.string(),
partnerBps: z.number(),
shapeshiftBps: z.number(),
isActive: z.boolean(),
diff --git a/packages/affiliate-dashboard/src/hooks/useAffiliateStats.ts b/packages/affiliate-dashboard/src/hooks/useAffiliateStats.ts
index 405d822e555..a6068263270 100644
--- a/packages/affiliate-dashboard/src/hooks/useAffiliateStats.ts
+++ b/packages/affiliate-dashboard/src/hooks/useAffiliateStats.ts
@@ -20,8 +20,8 @@ const ApiResponseSchema = z.object({
totalFeesEarnedUsd: NumericString,
})
-const fetchStats = async (address: string, period: Period): Promise => {
- const params = new URLSearchParams({ address })
+const fetchStats = async (partnerCode: string, period: Period): Promise => {
+ const params = new URLSearchParams({ partnerCode })
if (period.startDate) params.append('startDate', period.startDate)
if (period.endDate) params.append('endDate', period.endDate)
@@ -37,12 +37,12 @@ const fetchStats = async (address: string, period: Period): Promise =>
useQuery({
- queryKey: ['affiliate', 'stats', address, period.startDate, period.endDate],
- queryFn: () => fetchStats(address, period),
- enabled: Boolean(address),
+ queryKey: ['affiliate', 'stats', partnerCode, period.startDate, period.endDate],
+ queryFn: () => fetchStats(partnerCode, period),
+ enabled: Boolean(partnerCode),
placeholderData: keepPreviousData,
})
diff --git a/packages/affiliate-dashboard/src/hooks/useAffiliateSwaps.ts b/packages/affiliate-dashboard/src/hooks/useAffiliateSwaps.ts
index 36fb8c65fb7..f7f14591669 100644
--- a/packages/affiliate-dashboard/src/hooks/useAffiliateSwaps.ts
+++ b/packages/affiliate-dashboard/src/hooks/useAffiliateSwaps.ts
@@ -51,12 +51,12 @@ export interface AffiliateSwapsPage {
}
const fetchSwaps = async (
- address: string,
+ partnerCode: string,
period: Period,
cursor: string | undefined,
): Promise => {
const params = new URLSearchParams({
- address,
+ partnerCode,
limit: String(SWAPS_PER_PAGE),
})
@@ -69,13 +69,13 @@ const fetchSwaps = async (
}
export const useAffiliateSwaps = (
- address: string,
+ partnerCode: string,
period: Period,
): UseInfiniteQueryResult, Error> =>
useInfiniteQuery({
- queryKey: ['affiliate', 'swaps', address, period.key],
- queryFn: ({ pageParam }) => fetchSwaps(address, period, pageParam),
- enabled: Boolean(address),
+ queryKey: ['affiliate', 'swaps', partnerCode, period.key],
+ queryFn: ({ pageParam }) => fetchSwaps(partnerCode, period, pageParam),
+ enabled: Boolean(partnerCode),
initialPageParam: undefined as string | undefined,
getNextPageParam: lastPage => {
const next = lastPage.nextCursor?.trim()
diff --git a/packages/caip/package.json b/packages/caip/package.json
index 3b76dbd75ca..93bb60fa25c 100644
--- a/packages/caip/package.json
+++ b/packages/caip/package.json
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/caip",
- "version": "8.16.7",
+ "version": "8.16.9",
"description": "CAIP Implementation",
"repository": "https://github.com/shapeshift/web",
"license": "MIT",
diff --git a/packages/chain-adapters/package.json b/packages/chain-adapters/package.json
index 951a74b67d9..f8af39f8374 100644
--- a/packages/chain-adapters/package.json
+++ b/packages/chain-adapters/package.json
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/chain-adapters",
- "version": "11.3.9",
+ "version": "11.4.0",
"repository": "https://github.com/shapeshift/web",
"license": "MIT",
"type": "module",
diff --git a/packages/contracts/package.json b/packages/contracts/package.json
index 5454620ec88..e27a7d5148e 100644
--- a/packages/contracts/package.json
+++ b/packages/contracts/package.json
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/contracts",
- "version": "1.0.6",
+ "version": "1.0.7",
"repository": "https://github.com/shapeshift/web",
"license": "MIT",
"type": "module",
diff --git a/packages/contracts/src/abis/foxy.ts b/packages/contracts/src/abis/foxy.ts
deleted file mode 100644
index 448865b4145..00000000000
--- a/packages/contracts/src/abis/foxy.ts
+++ /dev/null
@@ -1,675 +0,0 @@
-export const FOXY_ABI = [
- {
- inputs: [],
- stateMutability: 'nonpayable',
- type: 'constructor',
- },
- {
- anonymous: false,
- inputs: [
- {
- indexed: true,
- internalType: 'address',
- name: 'owner',
- type: 'address',
- },
- {
- indexed: true,
- internalType: 'address',
- name: 'spender',
- type: 'address',
- },
- {
- indexed: false,
- internalType: 'uint256',
- name: 'value',
- type: 'uint256',
- },
- ],
- name: 'Approval',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- {
- indexed: true,
- internalType: 'uint256',
- name: 'epoch',
- type: 'uint256',
- },
- {
- indexed: false,
- internalType: 'uint256',
- name: 'rebase',
- type: 'uint256',
- },
- {
- indexed: false,
- internalType: 'uint256',
- name: 'index',
- type: 'uint256',
- },
- ],
- name: 'LogRebase',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- {
- indexed: true,
- internalType: 'uint256',
- name: 'epoch',
- type: 'uint256',
- },
- {
- indexed: false,
- internalType: 'uint256',
- name: 'timestamp',
- type: 'uint256',
- },
- {
- indexed: false,
- internalType: 'uint256',
- name: 'totalSupply',
- type: 'uint256',
- },
- ],
- name: 'LogSupply',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- {
- indexed: true,
- internalType: 'address',
- name: 'previousOwner',
- type: 'address',
- },
- {
- indexed: true,
- internalType: 'address',
- name: 'newOwner',
- type: 'address',
- },
- ],
- name: 'OwnershipPulled',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- {
- indexed: true,
- internalType: 'address',
- name: 'previousOwner',
- type: 'address',
- },
- {
- indexed: true,
- internalType: 'address',
- name: 'newOwner',
- type: 'address',
- },
- ],
- name: 'OwnershipPushed',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- {
- indexed: true,
- internalType: 'address',
- name: 'from',
- type: 'address',
- },
- {
- indexed: true,
- internalType: 'address',
- name: 'to',
- type: 'address',
- },
- {
- indexed: false,
- internalType: 'uint256',
- name: 'value',
- type: 'uint256',
- },
- ],
- name: 'Transfer',
- type: 'event',
- },
- {
- inputs: [],
- name: 'DOMAIN_SEPARATOR',
- outputs: [
- {
- internalType: 'bytes32',
- name: '',
- type: 'bytes32',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: '_owner',
- type: 'address',
- },
- {
- internalType: 'address',
- name: '_spender',
- type: 'address',
- },
- ],
- name: 'allowance',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: '_spender',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: '_value',
- type: 'uint256',
- },
- ],
- name: 'approve',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'uint256',
- name: '_gons',
- type: 'uint256',
- },
- ],
- name: 'balanceForGons',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: '_wallet',
- type: 'address',
- },
- ],
- name: 'balanceOf',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'circulatingSupply',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'decimals',
- outputs: [
- {
- internalType: 'uint8',
- name: '',
- type: 'uint8',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: '_spender',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: '_subtractedValue',
- type: 'uint256',
- },
- ],
- name: 'decreaseAllowance',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'getIndex',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'getOwner',
- outputs: [
- {
- internalType: 'address',
- name: '',
- type: 'address',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'uint256',
- name: '_amount',
- type: 'uint256',
- },
- ],
- name: 'gonsForBalance',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: '_spender',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: '_addedValue',
- type: 'uint256',
- },
- ],
- name: 'increaseAllowance',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'index',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: '_stakingContract',
- type: 'address',
- },
- ],
- name: 'initialize',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'initializer',
- outputs: [
- {
- internalType: 'address',
- name: '',
- type: 'address',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'name',
- outputs: [
- {
- internalType: 'string',
- name: '',
- type: 'string',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'owner',
- type: 'address',
- },
- ],
- name: 'nonces',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'owner',
- type: 'address',
- },
- {
- internalType: 'address',
- name: 'spender',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: 'value',
- type: 'uint256',
- },
- {
- internalType: 'uint256',
- name: 'deadline',
- type: 'uint256',
- },
- {
- internalType: 'uint8',
- name: 'v',
- type: 'uint8',
- },
- {
- internalType: 'bytes32',
- name: 'r',
- type: 'bytes32',
- },
- {
- internalType: 'bytes32',
- name: 's',
- type: 'bytes32',
- },
- ],
- name: 'permit',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'pullOwner',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: '_newOwner',
- type: 'address',
- },
- ],
- name: 'pushOwner',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'uint256',
- name: '_profit',
- type: 'uint256',
- },
- {
- internalType: 'uint256',
- name: '_epoch',
- type: 'uint256',
- },
- ],
- name: 'rebase',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- name: 'rebases',
- outputs: [
- {
- internalType: 'uint256',
- name: 'epoch',
- type: 'uint256',
- },
- {
- internalType: 'uint256',
- name: 'rebase',
- type: 'uint256',
- },
- {
- internalType: 'uint256',
- name: 'totalStakedBefore',
- type: 'uint256',
- },
- {
- internalType: 'uint256',
- name: 'totalStakedAfter',
- type: 'uint256',
- },
- {
- internalType: 'uint256',
- name: 'amountRebased',
- type: 'uint256',
- },
- {
- internalType: 'uint256',
- name: 'index',
- type: 'uint256',
- },
- {
- internalType: 'uint256',
- name: 'blockNumberOccurred',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'renounceOwner',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'stakingContract',
- outputs: [
- {
- internalType: 'address',
- name: '',
- type: 'address',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'symbol',
- outputs: [
- {
- internalType: 'string',
- name: '',
- type: 'string',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'totalSupply',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: '_to',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: '_value',
- type: 'uint256',
- },
- ],
- name: 'transfer',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: '_from',
- type: 'address',
- },
- {
- internalType: 'address',
- name: '_to',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: '_value',
- type: 'uint256',
- },
- ],
- name: 'transferFrom',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
-] as const
diff --git a/packages/contracts/src/abis/index.ts b/packages/contracts/src/abis/index.ts
index ced06a76a04..4b2adff5122 100644
--- a/packages/contracts/src/abis/index.ts
+++ b/packages/contracts/src/abis/index.ts
@@ -4,17 +4,12 @@ export * from './arbRetryableTx'
export * from './arbSys'
export * from './farming'
export * from './evergreenFarming'
-export * from './foxy'
export * from './foxyStaking'
export * from './l1ArbitrumGateway'
export * from './l1OrbitCustomGateway'
export * from './l2ArbitrumGateway'
-export * from './liquidityReserve'
export * from './rfox'
export * from './thorchainRouter'
-export * from './tokeManager'
-export * from './tokePool'
-export * from './tokeRewardHash'
export * from './iUniswapV2Pair'
export * from './uniswapV2Router02'
export * from './iUniswapV3Pool'
diff --git a/packages/contracts/src/abis/liquidityReserve.ts b/packages/contracts/src/abis/liquidityReserve.ts
deleted file mode 100644
index 05b2307bf2e..00000000000
--- a/packages/contracts/src/abis/liquidityReserve.ts
+++ /dev/null
@@ -1,547 +0,0 @@
-export const LIQUIDITY_RESERVE_ABI = [
- {
- inputs: [
- {
- internalType: 'address',
- name: '_stakingToken',
- type: 'address',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'constructor',
- },
- {
- anonymous: false,
- inputs: [
- {
- indexed: true,
- internalType: 'address',
- name: 'owner',
- type: 'address',
- },
- {
- indexed: true,
- internalType: 'address',
- name: 'spender',
- type: 'address',
- },
- {
- indexed: false,
- internalType: 'uint256',
- name: 'value',
- type: 'uint256',
- },
- ],
- name: 'Approval',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- {
- indexed: false,
- internalType: 'uint256',
- name: 'fee',
- type: 'uint256',
- },
- ],
- name: 'FeeChanged',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- {
- indexed: true,
- internalType: 'address',
- name: 'previousOwner',
- type: 'address',
- },
- {
- indexed: true,
- internalType: 'address',
- name: 'newOwner',
- type: 'address',
- },
- ],
- name: 'OwnershipPulled',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- {
- indexed: true,
- internalType: 'address',
- name: 'previousOwner',
- type: 'address',
- },
- {
- indexed: true,
- internalType: 'address',
- name: 'newOwner',
- type: 'address',
- },
- ],
- name: 'OwnershipPushed',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- {
- indexed: true,
- internalType: 'address',
- name: 'from',
- type: 'address',
- },
- {
- indexed: true,
- internalType: 'address',
- name: 'to',
- type: 'address',
- },
- {
- indexed: false,
- internalType: 'uint256',
- name: 'value',
- type: 'uint256',
- },
- ],
- name: 'Transfer',
- type: 'event',
- },
- {
- inputs: [],
- name: 'BASIS_POINTS',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'MINIMUM_LIQUIDITY',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'uint256',
- name: '_amount',
- type: 'uint256',
- },
- ],
- name: 'addLiquidity',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'owner',
- type: 'address',
- },
- {
- internalType: 'address',
- name: 'spender',
- type: 'address',
- },
- ],
- name: 'allowance',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'spender',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: 'amount',
- type: 'uint256',
- },
- ],
- name: 'approve',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'account',
- type: 'address',
- },
- ],
- name: 'balanceOf',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'decimals',
- outputs: [
- {
- internalType: 'uint8',
- name: '',
- type: 'uint8',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'spender',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: 'subtractedValue',
- type: 'uint256',
- },
- ],
- name: 'decreaseAllowance',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'fee',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'getOwner',
- outputs: [
- {
- internalType: 'address',
- name: '',
- type: 'address',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'spender',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: 'addedValue',
- type: 'uint256',
- },
- ],
- name: 'increaseAllowance',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: '_stakingContract',
- type: 'address',
- },
- {
- internalType: 'address',
- name: '_rewardToken',
- type: 'address',
- },
- ],
- name: 'initialize',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'initializer',
- outputs: [
- {
- internalType: 'address',
- name: '',
- type: 'address',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'uint256',
- name: '_amount',
- type: 'uint256',
- },
- {
- internalType: 'address',
- name: '_recipient',
- type: 'address',
- },
- ],
- name: 'instantUnstake',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'name',
- outputs: [
- {
- internalType: 'string',
- name: '',
- type: 'string',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'pullOwner',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: '_newOwner',
- type: 'address',
- },
- ],
- name: 'pushOwner',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'uint256',
- name: '_amount',
- type: 'uint256',
- },
- ],
- name: 'removeLiquidity',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'renounceOwner',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'rewardToken',
- outputs: [
- {
- internalType: 'address',
- name: '',
- type: 'address',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'uint256',
- name: '_fee',
- type: 'uint256',
- },
- ],
- name: 'setFee',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'stakingContract',
- outputs: [
- {
- internalType: 'address',
- name: '',
- type: 'address',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'stakingToken',
- outputs: [
- {
- internalType: 'address',
- name: '',
- type: 'address',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'symbol',
- outputs: [
- {
- internalType: 'string',
- name: '',
- type: 'string',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'totalSupply',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'recipient',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: 'amount',
- type: 'uint256',
- },
- ],
- name: 'transfer',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'sender',
- type: 'address',
- },
- {
- internalType: 'address',
- name: 'recipient',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: 'amount',
- type: 'uint256',
- },
- ],
- name: 'transferFrom',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'unstakeAllRewardTokens',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
-] as const
diff --git a/packages/contracts/src/abis/tokeManager.ts b/packages/contracts/src/abis/tokeManager.ts
deleted file mode 100644
index 81a0be56f22..00000000000
--- a/packages/contracts/src/abis/tokeManager.ts
+++ /dev/null
@@ -1,508 +0,0 @@
-export const TOKE_MANAGER_ABI = [
- { inputs: [], stateMutability: 'nonpayable', type: 'constructor' },
- {
- anonymous: false,
- inputs: [
- { indexed: false, internalType: 'bytes32', name: 'id', type: 'bytes32' },
- { indexed: false, internalType: 'address', name: 'controller', type: 'address' },
- ],
- name: 'ControllerRegistered',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: false, internalType: 'bytes32', name: 'id', type: 'bytes32' },
- { indexed: false, internalType: 'address', name: 'controller', type: 'address' },
- ],
- name: 'ControllerUnregistered',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [{ indexed: false, internalType: 'uint256', name: 'duration', type: 'uint256' }],
- name: 'CycleDurationSet',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [{ indexed: false, internalType: 'uint256', name: 'timestamp', type: 'uint256' }],
- name: 'CycleRolloverComplete',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [{ indexed: false, internalType: 'uint256', name: 'timestamp', type: 'uint256' }],
- name: 'CycleRolloverStarted',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: false, internalType: 'bytes32', name: 'controller', type: 'bytes32' },
- { indexed: false, internalType: 'address', name: 'adapaterAddress', type: 'address' },
- { indexed: false, internalType: 'bytes', name: 'data', type: 'bytes' },
- ],
- name: 'DeploymentStepExecuted',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: false, internalType: 'address', name: 'fxStateSender', type: 'address' },
- { indexed: false, internalType: 'address', name: 'destinationOnL2', type: 'address' },
- ],
- name: 'DestinationsSet',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [{ indexed: false, internalType: 'bool', name: 'eventSendSet', type: 'bool' }],
- name: 'EventSendSet',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: false, internalType: 'address', name: 'pool', type: 'address' },
- { indexed: false, internalType: 'uint256', name: 'amount', type: 'uint256' },
- ],
- name: 'LiquidityMovedToManager',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: false, internalType: 'address', name: 'pool', type: 'address' },
- { indexed: false, internalType: 'uint256', name: 'amount', type: 'uint256' },
- ],
- name: 'LiquidityMovedToPool',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: false, internalType: 'address[]', name: 'addresses', type: 'address[]' },
- { indexed: false, internalType: 'uint256[]', name: 'amounts', type: 'uint256[]' },
- ],
- name: 'ManagerSwept',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: false, internalType: 'uint256', name: 'nextCycleStartTime', type: 'uint256' },
- ],
- name: 'NextCycleStartSet',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [{ indexed: false, internalType: 'address', name: 'pool', type: 'address' }],
- name: 'PoolRegistered',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [{ indexed: false, internalType: 'address', name: 'pool', type: 'address' }],
- name: 'PoolUnregistered',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: true, internalType: 'bytes32', name: 'role', type: 'bytes32' },
- { indexed: true, internalType: 'bytes32', name: 'previousAdminRole', type: 'bytes32' },
- { indexed: true, internalType: 'bytes32', name: 'newAdminRole', type: 'bytes32' },
- ],
- name: 'RoleAdminChanged',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: true, internalType: 'bytes32', name: 'role', type: 'bytes32' },
- { indexed: true, internalType: 'address', name: 'account', type: 'address' },
- { indexed: true, internalType: 'address', name: 'sender', type: 'address' },
- ],
- name: 'RoleGranted',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: true, internalType: 'bytes32', name: 'role', type: 'bytes32' },
- { indexed: true, internalType: 'address', name: 'account', type: 'address' },
- { indexed: true, internalType: 'address', name: 'sender', type: 'address' },
- ],
- name: 'RoleRevoked',
- type: 'event',
- },
- {
- inputs: [],
- name: 'ADMIN_ROLE',
- outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'DEFAULT_ADMIN_ROLE',
- outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'MID_CYCLE_ROLE',
- outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'ROLLOVER_ROLE',
- outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'START_ROLLOVER_ROLE',
- outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: '_eventSend',
- outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'string', name: 'rewardsIpfsHash', type: 'string' }],
- name: 'completeRollover',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'currentCycle',
- outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'currentCycleIndex',
- outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'cycleDuration',
- outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- name: 'cycleRewardsHashes',
- outputs: [{ internalType: 'string', name: '', type: 'string' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'destinations',
- outputs: [
- { internalType: 'contract IFxStateSender', name: 'fxStateSender', type: 'address' },
- { internalType: 'address', name: 'destinationOnL2', type: 'address' },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- components: [
- {
- components: [
- { internalType: 'bytes32', name: 'controllerId', type: 'bytes32' },
- { internalType: 'bytes', name: 'data', type: 'bytes' },
- ],
- internalType: 'struct IManager.ControllerTransferData[]',
- name: 'cycleSteps',
- type: 'tuple[]',
- },
- ],
- internalType: 'struct IManager.MaintenanceExecution',
- name: 'params',
- type: 'tuple',
- },
- ],
- name: 'executeMaintenance',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- components: [
- {
- components: [
- { internalType: 'address', name: 'pool', type: 'address' },
- { internalType: 'uint256', name: 'amount', type: 'uint256' },
- ],
- internalType: 'struct IManager.PoolTransferData[]',
- name: 'poolData',
- type: 'tuple[]',
- },
- {
- components: [
- { internalType: 'bytes32', name: 'controllerId', type: 'bytes32' },
- { internalType: 'bytes', name: 'data', type: 'bytes' },
- ],
- internalType: 'struct IManager.ControllerTransferData[]',
- name: 'cycleSteps',
- type: 'tuple[]',
- },
- { internalType: 'address[]', name: 'poolsForWithdraw', type: 'address[]' },
- { internalType: 'bool', name: 'complete', type: 'bool' },
- { internalType: 'string', name: 'rewardsIpfsHash', type: 'string' },
- ],
- internalType: 'struct IManager.RolloverExecution',
- name: 'params',
- type: 'tuple',
- },
- ],
- name: 'executeRollover',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'getControllers',
- outputs: [{ internalType: 'bytes32[]', name: '', type: 'bytes32[]' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'getCurrentCycle',
- outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'getCurrentCycleIndex',
- outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'getCycleDuration',
- outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'getPools',
- outputs: [{ internalType: 'address[]', name: '', type: 'address[]' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'bytes32', name: 'role', type: 'bytes32' }],
- name: 'getRoleAdmin',
- outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'bytes32', name: 'role', type: 'bytes32' },
- { internalType: 'uint256', name: 'index', type: 'uint256' },
- ],
- name: 'getRoleMember',
- outputs: [{ internalType: 'address', name: '', type: 'address' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'bytes32', name: 'role', type: 'bytes32' }],
- name: 'getRoleMemberCount',
- outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'getRolloverStatus',
- outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'bytes32', name: 'role', type: 'bytes32' },
- { internalType: 'address', name: 'account', type: 'address' },
- ],
- name: 'grantRole',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'bytes32', name: 'role', type: 'bytes32' },
- { internalType: 'address', name: 'account', type: 'address' },
- ],
- name: 'hasRole',
- outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'uint256', name: '_cycleDuration', type: 'uint256' },
- { internalType: 'uint256', name: '_nextCycleStartTime', type: 'uint256' },
- ],
- name: 'initialize',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'nextCycleStartTime',
- outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'bytes32', name: 'id', type: 'bytes32' },
- { internalType: 'address', name: 'controller', type: 'address' },
- ],
- name: 'registerController',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'address', name: 'pool', type: 'address' }],
- name: 'registerPool',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
- name: 'registeredControllers',
- outputs: [{ internalType: 'address', name: '', type: 'address' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'bytes32', name: 'role', type: 'bytes32' },
- { internalType: 'address', name: 'account', type: 'address' },
- ],
- name: 'renounceRole',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'bytes32', name: 'role', type: 'bytes32' },
- { internalType: 'address', name: 'account', type: 'address' },
- ],
- name: 'revokeRole',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'rolloverStarted',
- outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'uint256', name: 'duration', type: 'uint256' }],
- name: 'setCycleDuration',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'address', name: '_fxStateSender', type: 'address' },
- { internalType: 'address', name: '_destinationOnL2', type: 'address' },
- ],
- name: 'setDestinations',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'bool', name: '_eventSendSet', type: 'bool' }],
- name: 'setEventSend',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'uint256', name: '_nextCycleStartTime', type: 'uint256' }],
- name: 'setNextCycleStartTime',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'bytes32', name: 'role', type: 'bytes32' }],
- name: 'setupRole',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'startCycleRollover',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'address[]', name: 'poolAddresses', type: 'address[]' }],
- name: 'sweep',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'bytes32', name: 'id', type: 'bytes32' }],
- name: 'unRegisterController',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'address', name: 'pool', type: 'address' }],
- name: 'unRegisterPool',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
-] as const
diff --git a/packages/contracts/src/abis/tokePool.ts b/packages/contracts/src/abis/tokePool.ts
deleted file mode 100644
index cf539b11f24..00000000000
--- a/packages/contracts/src/abis/tokePool.ts
+++ /dev/null
@@ -1,308 +0,0 @@
-export const TOKE_POOL_ABI = [
- {
- anonymous: false,
- inputs: [
- { indexed: true, internalType: 'address', name: 'owner', type: 'address' },
- { indexed: true, internalType: 'address', name: 'spender', type: 'address' },
- { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' },
- ],
- name: 'Approval',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: false, internalType: 'address', name: 'fxStateSender', type: 'address' },
- { indexed: false, internalType: 'address', name: 'destinationOnL2', type: 'address' },
- ],
- name: 'DestinationsSet',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [{ indexed: false, internalType: 'bool', name: 'eventSendSet', type: 'bool' }],
- name: 'EventSendSet',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: true, internalType: 'address', name: 'previousOwner', type: 'address' },
- { indexed: true, internalType: 'address', name: 'newOwner', type: 'address' },
- ],
- name: 'OwnershipTransferred',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [{ indexed: false, internalType: 'address', name: 'account', type: 'address' }],
- name: 'Paused',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: true, internalType: 'address', name: 'from', type: 'address' },
- { indexed: true, internalType: 'address', name: 'to', type: 'address' },
- { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' },
- ],
- name: 'Transfer',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [{ indexed: false, internalType: 'address', name: 'account', type: 'address' }],
- name: 'Unpaused',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: false, internalType: 'address', name: 'requestor', type: 'address' },
- { indexed: false, internalType: 'uint256', name: 'amount', type: 'uint256' },
- ],
- name: 'WithdrawalRequested',
- type: 'event',
- },
- {
- inputs: [],
- name: '_eventSend',
- outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'address', name: 'owner', type: 'address' },
- { internalType: 'address', name: 'spender', type: 'address' },
- ],
- name: 'allowance',
- outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'address', name: 'spender', type: 'address' },
- { internalType: 'uint256', name: 'amount', type: 'uint256' },
- ],
- name: 'approve',
- outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }],
- name: 'approveManager',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
- name: 'balanceOf',
- outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'decimals',
- outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'address', name: 'spender', type: 'address' },
- { internalType: 'uint256', name: 'subtractedValue', type: 'uint256' },
- ],
- name: 'decreaseAllowance',
- outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }],
- name: 'deposit',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'address', name: 'account', type: 'address' },
- { internalType: 'uint256', name: 'amount', type: 'uint256' },
- ],
- name: 'depositFor',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'destinations',
- outputs: [
- { internalType: 'contract IFxStateSender', name: 'fxStateSender', type: 'address' },
- { internalType: 'address', name: 'destinationOnL2', type: 'address' },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'address', name: 'spender', type: 'address' },
- { internalType: 'uint256', name: 'addedValue', type: 'uint256' },
- ],
- name: 'increaseAllowance',
- outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'contract ERC20Upgradeable', name: '_underlyer', type: 'address' },
- { internalType: 'contract IManager', name: '_manager', type: 'address' },
- { internalType: 'string', name: '_name', type: 'string' },
- { internalType: 'string', name: '_symbol', type: 'string' },
- ],
- name: 'initialize',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'manager',
- outputs: [{ internalType: 'contract IManager', name: '', type: 'address' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'name',
- outputs: [{ internalType: 'string', name: '', type: 'string' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'owner',
- outputs: [{ internalType: 'address', name: '', type: 'address' }],
- stateMutability: 'view',
- type: 'function',
- },
- { inputs: [], name: 'pause', outputs: [], stateMutability: 'nonpayable', type: 'function' },
- {
- inputs: [],
- name: 'paused',
- outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'renounceOwnership',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }],
- name: 'requestWithdrawal',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'address', name: '', type: 'address' }],
- name: 'requestedWithdrawals',
- outputs: [
- { internalType: 'uint256', name: 'minCycle', type: 'uint256' },
- { internalType: 'uint256', name: 'amount', type: 'uint256' },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'address', name: '_fxStateSender', type: 'address' },
- { internalType: 'address', name: '_destinationOnL2', type: 'address' },
- ],
- name: 'setDestinations',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'bool', name: '_eventSendSet', type: 'bool' }],
- name: 'setEventSend',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'symbol',
- outputs: [{ internalType: 'string', name: '', type: 'string' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'totalSupply',
- outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'address', name: 'recipient', type: 'address' },
- { internalType: 'uint256', name: 'amount', type: 'uint256' },
- ],
- name: 'transfer',
- outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'address', name: 'sender', type: 'address' },
- { internalType: 'address', name: 'recipient', type: 'address' },
- { internalType: 'uint256', name: 'amount', type: 'uint256' },
- ],
- name: 'transferFrom',
- outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }],
- name: 'transferOwnership',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'underlyer',
- outputs: [{ internalType: 'contract ERC20Upgradeable', name: '', type: 'address' }],
- stateMutability: 'view',
- type: 'function',
- },
- { inputs: [], name: 'unpause', outputs: [], stateMutability: 'nonpayable', type: 'function' },
- {
- inputs: [{ internalType: 'uint256', name: 'requestedAmount', type: 'uint256' }],
- name: 'withdraw',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'withheldLiquidity',
- outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- stateMutability: 'view',
- type: 'function',
- },
-] as const
diff --git a/packages/contracts/src/abis/tokeRewardHash.ts b/packages/contracts/src/abis/tokeRewardHash.ts
deleted file mode 100644
index 1a431ee0aa6..00000000000
--- a/packages/contracts/src/abis/tokeRewardHash.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-export const TOKE_REWARD_HASH_ABI = [
- { inputs: [], stateMutability: 'nonpayable', type: 'constructor' },
- {
- anonymous: false,
- inputs: [
- { indexed: false, internalType: 'uint256', name: 'cycleIndex', type: 'uint256' },
- { indexed: false, internalType: 'string', name: 'latestClaimableHash', type: 'string' },
- { indexed: false, internalType: 'string', name: 'cycleHash', type: 'string' },
- ],
- name: 'CycleHashAdded',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- { indexed: true, internalType: 'address', name: 'previousOwner', type: 'address' },
- { indexed: true, internalType: 'address', name: 'newOwner', type: 'address' },
- ],
- name: 'OwnershipTransferred',
- type: 'event',
- },
- {
- inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- name: 'cycleHashes',
- outputs: [
- { internalType: 'string', name: 'latestClaimable', type: 'string' },
- { internalType: 'string', name: 'cycle', type: 'string' },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'latestCycleIndex',
- outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'owner',
- outputs: [{ internalType: 'address', name: '', type: 'address' }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'renounceOwnership',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- { internalType: 'uint256', name: 'index', type: 'uint256' },
- { internalType: 'string', name: 'latestClaimableIpfsHash', type: 'string' },
- { internalType: 'string', name: 'cycleIpfsHash', type: 'string' },
- ],
- name: 'setCycleHashes',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }],
- name: 'transferOwnership',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
-] as const
diff --git a/packages/public-api/docs/introduction.md b/packages/public-api/docs/introduction.md
index 0b038a18743..048f7d2daf6 100644
--- a/packages/public-api/docs/introduction.md
+++ b/packages/public-api/docs/introduction.md
@@ -1,17 +1,49 @@
-The ShapeShift Public API enables developers to integrate multi-chain swap functionality into their applications. Access rates from multiple DEX aggregators and execute swaps across supported blockchains.
+The ShapeShift Public API lets you integrate multi-chain swap functionality into your application. Fetch rates from multiple DEX aggregators and bridges, build executable quotes, and track swaps across supported blockchains.
-There are two ways to integrate:
+## Base URL
-1. **Swap Widget SDK** — Drop-in React component with built-in UI, wallet connection, and multi-chain support. Fastest way to integrate.
-2. **REST API** — Build your own swap UI using the endpoints below. Full control over UX.
+```
+https://api.shapeshift.com
+```
-## Affiliate Tracking (Optional)
+All endpoints are versioned under `/v1` (e.g. `https://api.shapeshift.com/v1/swap/rates`). This interactive reference is served at `https://api.shapeshift.com/docs`, and the raw OpenAPI document at `https://api.shapeshift.com/docs/json`.
-Include a `X-Partner-Code` header with your registered partner code (e.g. `vultisig`, `venice`) to attribute swaps for affiliate fee tracking. The API resolves the code to the registered affiliate address and BPS automatically. Register a partner code at the affiliate dashboard. This is optional — all endpoints work without it.
+## Two ways to integrate
-## Asset IDs
+1. **Swap Widget SDK** — a drop-in React component with built-in UI, wallet connection, and multi-chain support. The fastest path. See the **Swap Widget SDK** section.
+2. **REST API** — call the endpoints directly and build your own UI for full control over UX. See the **REST API Guide** section, then the per-endpoint reference below.
+
+## Affiliate tracking (optional)
+
+Send an `X-Partner-Code` header with your registered partner code (e.g. `your-partner-code`) on the swap endpoints to attribute swaps for affiliate revenue share. The API attributes the swap to your affiliate account and applies your configured fee (bps) automatically. All endpoints work without it — unattributed swaps use the default fee. See the [Affiliate Program guide](https://github.com/shapeshift/web/blob/develop/docs/affiliates.md) for how to obtain a code.
+
+## Asset IDs (CAIP-19)
+
+Assets are identified with [CAIP-19](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-19.md): `{chainId}/{assetNamespace}:{assetReference}`
-Assets use CAIP-19 format: `{chainId}/{assetNamespace}:{assetReference}`
- Native ETH: `eip155:1/slip44:60`
- USDC on Ethereum: `eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48`
- Native BTC: `bip122:000000000019d6689c085ae165831e93/slip44:0`
+
+Chains use [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) (e.g. `eip155:1`). Use `GET /v1/chains` and `GET /v1/assets` to discover supported values.
+
+## Errors
+
+Errors return the appropriate HTTP status and a JSON body:
+
+```json
+{ "error": "Human-readable message", "code": "MACHINE_CODE", "details": [] }
+```
+
+`code` and `details` are present where applicable (e.g. `QUOTE_NOT_FOUND`, `TX_HASH_REQUIRED`, `TX_HASH_MISMATCH`, `RATE_LIMIT_EXCEEDED`, validation `details`).
+
+## Rate limiting
+
+Endpoints are rate limited per IP on a fixed 60-second window. A global limit applies across all endpoints, and individual endpoint groups (data, rates, quote, status, affiliate) have their own independent limits on top of it — so a request counts against both. When either is exceeded, the API returns `429` with code `RATE_LIMIT_EXCEEDED` and these headers:
+
+- `Retry-After` — seconds until the window resets
+- `RateLimit-Limit` — max requests allowed per window
+- `RateLimit-Remaining` — requests remaining in the current window
+- `RateLimit-Reset` — seconds until the window resets
+
+Back off using `Retry-After` and avoid polling faster than necessary (see the REST API Guide for polling guidance).
diff --git a/packages/public-api/docs/rest-api-guide.md b/packages/public-api/docs/rest-api-guide.md
index 23a1f9a5b1e..1092ac48403 100644
--- a/packages/public-api/docs/rest-api-guide.md
+++ b/packages/public-api/docs/rest-api-guide.md
@@ -1,42 +1,80 @@
-Step-by-step guide for integrating swaps via the REST API.
+A step-by-step guide to executing a swap via the REST API. Full request/response schemas for every endpoint are in the reference sections below — this guide covers the flow and the semantics that aren't obvious from the schemas alone (quote expiry, status polling, errors).
+
+All paths are relative to `https://api.shapeshift.com`. Send `X-Partner-Code: ` on the swap endpoints to attribute swaps for affiliate revenue (optional).
+
+## 1. Discover chains and assets
-## 1. Get Supported Chains
```
GET /v1/chains
+GET /v1/assets?chainId=eip155:1&limit=100&offset=0
```
-## 2. Get Supported Assets
-```
-GET /v1/assets
-```
+`GET /v1/assets` supports optional `chainId`, `limit` (1–1000, default 100), and `offset` (default 0) query params for filtering and pagination. Use `GET /v1/assets/count` to size pagination. Look up a single asset with `GET /v1/assets/{assetId}` (the asset ID is a full CAIP-19 string).
+
+## 2. Get rates
-## 3. Get Swap Rates
```
GET /v1/swap/rates?sellAssetId=eip155:1/slip44:60&buyAssetId=eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&sellAmountCryptoBaseUnit=1000000000000000000
-X-Partner-Code: your-partner-code (optional)
+X-Partner-Code: your-partner-code
```
-## 4. Get Executable Quote
+Optional `slippageTolerancePercentageDecimal` (e.g. `0.01` for 1%). The response returns a `rates` array (one entry per swapper, each with its own `swapperName`, amounts, fees, and an optional per-swapper `error`) plus `timestamp` and `expiresAt`. **Rates are indicative**, expire quickly (`expiresAt` ≈ 30s after issue), and are for display/comparison — request a quote to execute.
+
+## 3. Get an executable quote
+
```
POST /v1/swap/quote
-X-Partner-Code: your-partner-code (optional)
+Content-Type: application/json
+X-Partner-Code: your-partner-code
{
"sellAssetId": "eip155:1/slip44:60",
"buyAssetId": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"sellAmountCryptoBaseUnit": "1000000000000000000",
"swapperName": "Relay",
- "receiveAddress": "bc1q...",
- "sendAddress": "0x..."
+ "receiveAddress": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
+ "sendAddress": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
+ "slippageTolerancePercentageDecimal": "0.01",
+ "accountNumber": 0
}
```
-## 5. Execute the Swap
-Use the returned `transactionData` to build and sign a transaction with the user's wallet, then broadcast it to the network.
+- `swapperName` comes from the rate you chose in step 2.
+- `slippageTolerancePercentageDecimal` is optional; `accountNumber` is optional (defaults to `0`) and is needed for chains that derive addresses per account index (e.g. UTXO/Cosmos).
+- The response includes a `quoteId` (needed for status tracking), an `approval` object (whether an ERC-20 approval is required and the approval tx to send first), and a `steps` array. Each step may include `transactionData` — a discriminated union on `type` (`evm`, `solana`, `utxo_psbt`, `utxo_deposit`, `cosmos`) — describing exactly what to sign for that chain.
+- Quotes expire: honor the `expiresAt` timestamp (≈ 60s after issue). Request a fresh quote rather than submitting an expired one.
+
+## 4. Execute the swap
+
+The API does **not** broadcast transactions — your application signs and broadcasts with the user's wallet:
+
+1. If `approval.isRequired` is true and `approval.approvalTx` is present, send the approval transaction first and wait for it to confirm.
+2. For each step with `transactionData`, build, sign, and broadcast the transaction according to its `type` (EVM tx, Solana instructions, UTXO PSBT/deposit, or Cosmos message).
+3. Capture the resulting transaction hash for status tracking.
+
+## 5. Track status
-## 6. Check Swap Status
```
GET /v1/swap/status?quoteId=&txHash=0x...
```
-On the **first call**, include `txHash` to bind the transaction to the quote and start tracking. Subsequent polls can omit `txHash`.
+- On the **first call after broadcasting**, include `txHash` to bind it to the quote and begin tracking. This sets status to `submitted`. Subsequent polls can omit `txHash`.
+- `status` is one of `submitted`, `confirmed`, `failed`. Poll until `confirmed` or `failed`; a `buyTxHash` appears once the destination transaction is known.
+- Poll at a modest interval (e.g. every 5–15s) and respect rate-limit headers. Stop polling on a terminal status.
+
+### Status errors
+
+- `404` `QUOTE_NOT_FOUND` — the quote is unknown or has expired from the store. Request a new quote.
+- `400` `TX_HASH_REQUIRED` — no `txHash` was provided and none is bound yet; pass the broadcast tx hash.
+- `409` `TX_HASH_MISMATCH` — a different `txHash` is already bound to this quote.
+
+## Affiliate reporting (optional)
+
+Once live, partners can review attributed activity by partner code:
+
+```
+GET /v1/affiliate/stats?partnerCode=your-partner-code
+GET /v1/affiliate/swaps?partnerCode=your-partner-code
+```
+
+You can also resolve a code to its attribution details (partner address and bps split) with `GET /v1/partner/{code}`.
diff --git a/packages/public-api/docs/swap-widget-sdk.md b/packages/public-api/docs/swap-widget-sdk.md
index d8a244da3c3..54f64802f7b 100644
--- a/packages/public-api/docs/swap-widget-sdk.md
+++ b/packages/public-api/docs/swap-widget-sdk.md
@@ -1,20 +1,26 @@
-The `@shapeshiftoss/swap-widget` package is a drop-in React component that provides a complete swap interface. It handles asset selection, rate comparison, wallet connection, transaction signing, and status tracking.
+The `@shapeshiftoss/swap-widget` package is a drop-in React component that provides a complete swap interface — asset selection, rate comparison, wallet connection, transaction signing, and status tracking — backed by this API.
+
+> 📖 **The canonical, always-current reference is the package README:**
+> [`packages/swap-widget/README.md`](https://github.com/shapeshift/web/blob/develop/packages/swap-widget/README.md).
+> It documents every prop, the theming API, supported chains/swappers, and exported hooks. This page is a short orientation; defer to the README for details.
## Installation
```bash
npm install @shapeshiftoss/swap-widget
-# or
-yarn add @shapeshiftoss/swap-widget
```
-**Peer dependencies** (install alongside the widget):
+Install the peer dependencies alongside it (React, wagmi/viem, React Query, and Reown AppKit):
```bash
-npm install react react-dom
+npm install react react-dom wagmi @wagmi/core viem \
+ @tanstack/react-query \
+ @reown/appkit @reown/appkit-adapter-wagmi \
+ @reown/appkit-adapter-bitcoin @reown/appkit-adapter-solana \
+ @solana/wallet-adapter-wallets @solana/web3.js
```
-**CSS** — You must import the widget stylesheet:
+Import the stylesheet once (required for the widget to render correctly):
```tsx
import '@shapeshiftoss/swap-widget/style.css'
@@ -23,351 +29,27 @@ import '@shapeshiftoss/swap-widget/style.css'
## Quick Start
```tsx
-import { SwapWidget } from '@shapeshiftoss/swap-widget'
import '@shapeshiftoss/swap-widget/style.css'
-function App() {
- return (
- console.log('Success:', txHash)}
- />
- )
-}
-```
-
----
-
-## Wallet Connection Modes
-
-The widget supports two wallet connection strategies. Choose the one that matches your application.
-
-### Mode 1: External Wallet (Recommended for dApps)
-
-**Use this if your application already has a wallet connection** (wagmi, ethers, viem, RainbowKit, ConnectKit, AppKit, etc.). Pass the connected wallet to the widget — no duplicate wallet modals.
-
-```tsx
import { SwapWidget } from '@shapeshiftoss/swap-widget'
-import '@shapeshiftoss/swap-widget/style.css'
-import { useWalletClient } from 'wagmi'
-
-function SwapPage() {
- const { data: walletClient } = useWalletClient()
-
- return (
- {
- // Trigger YOUR app's wallet connection modal
- openYourConnectModal()
- }}
- theme="dark"
- />
- )
-}
-```
-
-| Prop | Purpose |
-|------|---------|
-| `walletClient` | A viem `WalletClient` from your existing wallet setup |
-| `onConnectWallet` | Called when the user clicks "Connect" inside the widget — open your own modal |
-| `enableWalletConnection` | Leave as `false` (default) — the widget won't render its own connect UI |
-
-This mode creates its own read-only wagmi config internally for balance fetching. It does **not** interfere with your application's wagmi provider or AppKit instance.
-
-### Mode 2: Built-in Wallet Connection (Standalone)
-
-**Use this if your page has no wallet infrastructure.** The widget manages wallet connections internally via Reown AppKit, supporting EVM, Bitcoin, and Solana wallets.
-
-```tsx
-import { SwapWidget } from '@shapeshiftoss/swap-widget'
-import '@shapeshiftoss/swap-widget/style.css'
function App() {
return (
console.log('Success:', txHash)}
/>
)
}
```
-Get a WalletConnect project ID at [cloud.walletconnect.com](https://cloud.walletconnect.com).
-
-When `enableWalletConnection` is true, the widget:
-- Shows a "Connect" button that opens a multi-chain wallet modal
-- Supports MetaMask, WalletConnect, Coinbase Wallet, and other EVM wallets
-- Supports Bitcoin wallets via WalletConnect
-- Supports Phantom, Solflare, and other Solana wallets
-
-> **Important: AppKit Singleton Constraint**
->
-> The built-in wallet connection uses Reown AppKit, which is a **global singleton** — only one AppKit instance can exist per page. If your page already uses AppKit or Web3Modal, the widget's modal will conflict with yours.
->
-> **If your dApp already has AppKit/Web3Modal**: Use **Mode 1 (External Wallet)** instead. Pass your connected `walletClient` to the widget and handle wallet connection yourself.
->
-> **If your page has no wallet setup**: Mode 2 works perfectly — the widget is the only AppKit instance on the page.
-
----
-
-## Props Reference
-
-### Core Props
-
-| Prop | Type | Default | Description |
-|------|------|---------|-------------|
-| `partnerCode` | `string` | — | Your registered partner code for affiliate fee attribution. Register at the affiliate dashboard. |
-| `apiBaseUrl` | `string` | — | Custom API base URL |
-| `theme` | `ThemeMode \| ThemeConfig` | `"dark"` | Theme mode or full theme configuration |
-| `showPoweredBy` | `boolean` | `true` | Show "Powered by ShapeShift" branding |
-| `defaultSlippage` | `string` | `"0.5"` | Default slippage tolerance percentage |
-| `ratesRefetchInterval` | `number` | `15000` | Rate refresh interval in milliseconds |
-
-### Wallet Props
-
-| Prop | Type | Default | Description |
-|------|------|---------|-------------|
-| `walletClient` | `WalletClient` | — | Viem wallet client for EVM transactions (Mode 1) |
-| `enableWalletConnection` | `boolean` | `false` | Enable built-in wallet modal (Mode 2) |
-| `walletConnectProjectId` | `string` | — | Required for Mode 2 |
-| `onConnectWallet` | `() => void` | — | Callback when user clicks "Connect" (Mode 1) |
-| `defaultReceiveAddress` | `string` | — | Lock the receive address to a specific value |
-
-### Asset Filtering Props
-
-| Prop | Type | Default | Description |
-|------|------|---------|-------------|
-| `defaultSellAsset` | `Asset` | ETH | Initial sell asset |
-| `defaultBuyAsset` | `Asset` | USDC | Initial buy asset |
-| `allowedChainIds` | `ChainId[]` | all | Restrict both sides to these chains |
-| `disabledChainIds` | `ChainId[]` | `[]` | Hide chains from both selectors |
-| `disabledAssetIds` | `AssetId[]` | `[]` | Hide assets from both selectors |
-| `sellAllowedChainIds` | `ChainId[]` | — | Restrict sell side to these chains |
-| `buyAllowedChainIds` | `ChainId[]` | — | Restrict buy side to these chains |
-| `sellAllowedAssetIds` | `AssetId[]` | — | Restrict sell side to these assets |
-| `buyAllowedAssetIds` | `AssetId[]` | — | Restrict buy side to these assets |
-| `sellDisabledChainIds` | `ChainId[]` | `[]` | Hide chains from sell selector |
-| `buyDisabledChainIds` | `ChainId[]` | `[]` | Hide chains from buy selector |
-| `sellDisabledAssetIds` | `AssetId[]` | `[]` | Hide assets from sell selector |
-| `buyDisabledAssetIds` | `AssetId[]` | `[]` | Hide assets from buy selector |
-| `allowedSwapperNames` | `SwapperName[]` | all | Restrict to specific swappers |
-| `isBuyAssetLocked` | `boolean` | `false` | Prevent changing the buy asset |
-
-### Callback Props
-
-| Prop | Type | Description |
-|------|-------|-------------|
-| `onSwapSuccess` | `(txHash: string) => void` | Called when a swap succeeds |
-| `onSwapError` | `(error: Error) => void` | Called when a swap fails |
-| `onAssetSelect` | `(type: 'sell' \| 'buy', asset: Asset) => void` | Called when user selects an asset |
-
----
-
-## Theming
-
-### Simple Mode
-
-```tsx
-
-
-```
-
-### Custom Theme
-
-```tsx
-const theme: ThemeConfig = {
- mode: 'dark',
- accentColor: '#3861fb',
- backgroundColor: '#0a0a14',
- cardColor: '#12121c',
- textColor: '#ffffff',
- borderRadius: '12px',
- fontFamily: 'Inter, sans-serif',
- borderColor: '#2a2a3e',
- secondaryTextColor: '#a0a0b0',
- mutedTextColor: '#6b6b80',
- inputColor: '#1a1a2e',
- hoverColor: '#1e1e32',
- buttonVariant: 'filled', // 'filled' or 'outline'
-}
-
-
-```
-
-| Property | Type | Description |
-|----------|------|-------------|
-| `mode` | `'light' \| 'dark'` | Base theme mode (required) |
-| `accentColor` | `string` | Buttons, focus states, active elements |
-| `backgroundColor` | `string` | Widget background |
-| `cardColor` | `string` | Card and panel backgrounds |
-| `textColor` | `string` | Primary text |
-| `borderRadius` | `string` | Border radius (e.g. `'12px'`) |
-| `fontFamily` | `string` | Font family |
-| `borderColor` | `string` | Border colors |
-| `secondaryTextColor` | `string` | Secondary labels |
-| `mutedTextColor` | `string` | Muted/disabled text |
-| `inputColor` | `string` | Input field background |
-| `hoverColor` | `string` | Hover state background |
-| `buttonVariant` | `'filled' \| 'outline'` | Button style |
-
----
-
-## Integration Examples
-
-### Restrict to Ethereum + Polygon Only
-
-```tsx
-import { SwapWidget, EVM_CHAIN_IDS } from '@shapeshiftoss/swap-widget'
-
-
-```
-
-### Lock Buy Asset (Payment Widget)
-
-```tsx
-const usdcAsset = {
- assetId: 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
- chainId: 'eip155:1',
- symbol: 'USDC',
- name: 'USD Coin',
- precision: 6,
-}
-
-
-```
-
-### Use Specific Swappers Only
-
-```tsx
-import { SwapWidget, SwapperName } from '@shapeshiftoss/swap-widget'
-
-
-```
+## Key things to know
----
+- **Wallet connection is built in.** The widget connects wallets via Reown AppKit (EVM, Bitcoin, and Solana).
+- **AppKit must be initialized before the widget mounts.** Either pass `walletConnectProjectId` and the widget initializes AppKit for you (get a free project ID at [dashboard.reown.com](https://dashboard.reown.com)), or call `createAppKit()` yourself in the host app (with a wagmi EVM adapter) before mounting the widget — the widget detects the shared singleton and supplies its own providers, so you wrap it in none. Pair the host-owned path with `showConnectButton={false}` to drive connection from your own UI. This requires `@reown/appkit*`/`wagmi`/`viem` to dedupe to a single shared copy. See the [README](https://github.com/shapeshift/web/blob/develop/packages/swap-widget/README.md#wallet-connection).
+- **`partnerCode` drives affiliate attribution.** It is forwarded to this API as the `X-Partner-Code` header. See the [Affiliate Program guide](https://github.com/shapeshift/web/blob/develop/docs/affiliates.md).
+- **Chain/asset filtering** uses the `sellFilters` and `buyFilters` props (objects with `allowedChainIds` / `disabledChainIds` / `allowedAssetIds` / `disabledAssetIds`). See the README for the full prop list and examples.
-## Exported Hooks
-
-These hooks can be used outside the widget to build custom UI with ShapeShift asset data.
-
-```tsx
-import {
- useAssets,
- useAssetById,
- useChains,
- useAssetsByChainId,
- useAssetSearch,
-} from '@shapeshiftoss/swap-widget'
-```
-
-| Hook | Return Type | Description |
-|------|-------------|-------------|
-| `useAssets()` | `{ data: Asset[], isLoading, ... }` | All available assets |
-| `useAssetById(assetId)` | `{ data: Asset \| undefined, ... }` | Single asset by CAIP-19 ID |
-| `useChains()` | `{ data: ChainInfo[], ... }` | All chains with native assets |
-| `useAssetsByChainId(chainId)` | `{ data: Asset[], ... }` | All assets on a chain |
-| `useAssetSearch(query, chainId?)` | `{ data: Asset[], ... }` | Search by symbol or name |
-
-All hooks return React Query result objects with `data`, `isLoading`, `error`, `refetch`, etc.
-
-## Exported Utilities
-
-```tsx
-import {
- formatAmount,
- parseAmount,
- truncateAddress,
- isEvmChainId,
- getEvmNetworkId,
- getChainType,
- getChainName,
- getChainIcon,
- getChainColor,
- getBaseAsset,
- getExplorerTxLink,
- EVM_CHAIN_IDS,
- UTXO_CHAIN_IDS,
- COSMOS_CHAIN_IDS,
- OTHER_CHAIN_IDS,
-} from '@shapeshiftoss/swap-widget'
-```
-
----
-
-## Supported Chains
-
-The widget natively supports all EVM chains, Bitcoin, and Solana. Other chains (Cosmos, Starknet, NEAR, TON, Tron, Sui, etc.) are available via redirect to [app.shapeshift.com](https://app.shapeshift.com).
-
-| Chain | Chain ID | Type |
-|-------|----------|------|
-| Ethereum | `eip155:1` | EVM |
-| Arbitrum One | `eip155:42161` | EVM |
-| Avalanche C-Chain | `eip155:43114` | EVM |
-| Base | `eip155:8453` | EVM |
-| Berachain | `eip155:80094` | EVM |
-| Blast | `eip155:81457` | EVM |
-| BNB Smart Chain | `eip155:56` | EVM |
-| BOB | `eip155:60808` | EVM |
-| Cronos | `eip155:25` | EVM |
-| Flow EVM | `eip155:747` | EVM |
-| Gnosis | `eip155:100` | EVM |
-| Hemi | `eip155:43111` | EVM |
-| HyperEVM | `eip155:999` | EVM |
-| Ink | `eip155:57073` | EVM |
-| Katana | `eip155:747474` | EVM |
-| Linea | `eip155:59144` | EVM |
-| Mantle | `eip155:5000` | EVM |
-| MegaETH | `eip155:4326` | EVM |
-| Mode | `eip155:34443` | EVM |
-| Monad | `eip155:143` | EVM |
-| Optimism | `eip155:10` | EVM |
-| Plasma | `eip155:9745` | EVM |
-| Plume | `eip155:98866` | EVM |
-| Polygon | `eip155:137` | EVM |
-| Scroll | `eip155:534352` | EVM |
-| Soneium | `eip155:1868` | EVM |
-| Sonic | `eip155:146` | EVM |
-| Story | `eip155:1514` | EVM |
-| Unichain | `eip155:130` | EVM |
-| World Chain | `eip155:480` | EVM |
-| zkSync Era | `eip155:324` | EVM |
-| Bitcoin | `bip122:000000000019d6689c085ae165831e93` | UTXO |
-| Solana | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | Solana |
-
-## Supported Swappers (15)
-
-THORChain, MAYAChain, CoW Swap, 0x, Portals, Chainflip, Jupiter, Relay, ButterSwap, Bebop, Arbitrum Bridge, NEAR Intents, Cetus, Sun.io, AVNU.
-
----
-
-## Architecture Notes
-
-**Internal QueryClient** — The widget manages its own React Query `QueryClient`. You do not need to wrap it in a `QueryClientProvider`.
-
-**Wagmi Isolation** — In external wallet mode, the widget creates its own isolated read-only wagmi config for balance fetching. It does not interfere with your application's `WagmiProvider`.
-
-**AppKit Singleton** — In built-in wallet mode (`enableWalletConnection=true`), the widget uses Reown AppKit which is a page-level singleton. Only one AppKit instance can exist per page. If your dApp already uses AppKit or Web3Modal, you **must** use external wallet mode instead.
-
-**CSS Isolation** — All widget styles are prefixed with `ssw-` to avoid conflicts with host page styles. Import the stylesheet explicitly:
-
-```tsx
-import '@shapeshiftoss/swap-widget/style.css'
-```
+For the complete props reference, theming options, supported chains and swappers, and exported utilities/hooks, see the [package README](https://github.com/shapeshift/web/blob/develop/packages/swap-widget/README.md).
diff --git a/packages/public-api/src/lib/quoteStore.ts b/packages/public-api/src/lib/quoteStore.ts
index 46504a9fa66..60d8d6a2615 100644
--- a/packages/public-api/src/lib/quoteStore.ts
+++ b/packages/public-api/src/lib/quoteStore.ts
@@ -10,6 +10,7 @@ export type StoredQuote = {
sendAddress: string
receiveAddress: string
partnerAddress?: string
+ partnerCode?: string
partnerBps?: string
shapeshiftBps: string
affiliateBps: string
diff --git a/packages/public-api/src/routes/affiliate/getAffiliateStats.ts b/packages/public-api/src/routes/affiliate/getAffiliateStats.ts
index 6b76a1ecc4e..4572a1df015 100644
--- a/packages/public-api/src/routes/affiliate/getAffiliateStats.ts
+++ b/packages/public-api/src/routes/affiliate/getAffiliateStats.ts
@@ -14,7 +14,7 @@ registry.registerPath({
operationId: 'getAffiliateStats',
summary: 'Get affiliate statistics',
description:
- 'Retrieve aggregated swap statistics for an affiliate address. Returns total swaps, volume, and fees earned. Supports optional date range filtering.',
+ 'Retrieve aggregated swap statistics for an affiliate by partnerCode. Returns total swaps, volume, and fees earned. Supports optional date range filtering.',
tags: ['Affiliate'],
request: {
query: AffiliateStatsRequestSchema,
@@ -44,10 +44,10 @@ export const getAffiliateStats = async (req: Request, res: Response): Promise => {
receiveAddress,
sendAddress,
partnerAddress: req.affiliateInfo?.partnerAddress,
+ partnerCode: req.affiliateInfo?.partnerCode,
createdAt: now,
expiresAt: now + QuoteStore.QUOTE_TTL_MS,
metadata: {
diff --git a/packages/public-api/src/routes/status/utils.ts b/packages/public-api/src/routes/status/utils.ts
index 606a0126fb0..bbfc7e67051 100644
--- a/packages/public-api/src/routes/status/utils.ts
+++ b/packages/public-api/src/routes/status/utils.ts
@@ -22,6 +22,7 @@ const buildSwapRegistrationBody = (storedQuote: ReturnType **This README is the canonical reference for the swap widget.** Other docs (including the ShapeShift Public API docs) link here.
## Table of Contents
- [Installation](#installation)
- [Quick Start](#quick-start)
+- [Wallet Connection](#wallet-connection)
- [Props Reference](#props-reference)
+- [Filtering Chains and Assets](#filtering-chains-and-assets)
- [Theming](#theming)
- [Examples](#examples)
- [Exported Types](#exported-types)
- [Exported Utilities](#exported-utilities)
- [Exported Hooks](#exported-hooks)
- [Supported Chains](#supported-chains)
+- [Supported Swappers](#supported-swappers)
+- [Partner Codes & Affiliate Revenue](#partner-codes--affiliate-revenue)
- [Notes and Limitations](#notes-and-limitations)
## Installation
```bash
-pnpm add @shapeshiftoss/swap-widget
-# or
npm install @shapeshiftoss/swap-widget
```
### Peer Dependencies
-This package requires React 18 or later:
+The widget relies on React, wagmi/viem, React Query, and Reown AppKit (used internally for wallet
+connection). The widget initializes AppKit with the EVM, Bitcoin, and Solana adapters at load, so all
+of these peers are required — install them alongside the package:
-```json
-{
- "peerDependencies": {
- "react": ">=18.0.0",
- "react-dom": ">=18.0.0"
- }
-}
+```bash
+npm install react react-dom \
+ wagmi @wagmi/core viem \
+ @tanstack/react-query \
+ @reown/appkit @reown/appkit-adapter-wagmi \
+ @reown/appkit-adapter-bitcoin @reown/appkit-adapter-solana \
+ @solana/wallet-adapter-wallets @solana/web3.js
+```
+
+- **React 18 or 19** is supported (`^18.0.0 || ^19.0.0`).
+
+### Import the stylesheet
+
+The widget ships a stylesheet that **must** be imported once for it to render correctly:
+
+```tsx
+import '@shapeshiftoss/swap-widget/style.css'
```
## Quick Start
```tsx
-import { SwapWidget } from "@shapeshiftoss/swap-widget";
+import '@shapeshiftoss/swap-widget/style.css'
+
+import { SwapWidget } from '@shapeshiftoss/swap-widget'
function App() {
return (
console.log("Success:", txHash)}
- onSwapError={(error) => console.error("Error:", error)}
+ onSwapSuccess={txHash => console.log('Success:', txHash)}
+ onSwapError={error => console.error('Error:', error)}
/>
- );
+ )
}
```
-## Props Reference
+## Wallet Connection
-### SwapWidgetProps
-
-| Prop | Type | Default | Description |
-| ------------------------ | ----------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |
-| `partnerCode` | `string` | - | Your registered partner code for affiliate fee attribution. Register at the affiliate dashboard. |
-| `apiBaseUrl` | `string` | - | Custom API base URL. Useful for testing or custom deployments. |
-| `defaultSellAsset` | `Asset` | ETH on Ethereum | Initial asset to sell. |
-| `defaultBuyAsset` | `Asset` | USDC on Ethereum | Initial asset to buy. |
-| `disabledChainIds` | `ChainId[]` | `[]` | Chain IDs to hide from the asset selector. |
-| `disabledAssetIds` | `AssetId[]` | `[]` | Asset IDs to hide from the asset selector. |
-| `allowedChainIds` | `ChainId[]` | - | If provided, only show assets from these chains. Use this to restrict the widget to specific chains. |
-| `allowedAssetIds` | `AssetId[]` | - | If provided, only show these specific assets. |
-| `walletClient` | `WalletClient` | - | Viem wallet client for executing EVM transactions. |
-| `onConnectWallet` | `() => void` | - | Callback when user clicks "Connect Wallet" button. |
-| `onSwapSuccess` | `(txHash: string) => void` | - | Callback when a swap transaction succeeds. |
-| `onSwapError` | `(error: Error) => void` | - | Callback when a swap transaction fails. |
-| `onAssetSelect` | `(type: "sell" \| "buy", asset: Asset) => void` | - | Callback when user selects an asset. |
-| `theme` | `ThemeMode \| ThemeConfig` | `"dark"` | Theme mode (`"light"` or `"dark"`) or full theme configuration. |
-| `defaultSlippage` | `string` | `"0.5"` | Default slippage tolerance percentage. |
-| `showPoweredBy` | `boolean` | `true` | Show "Powered by ShapeShift" branding. |
-| `enableWalletConnection` | `boolean` | `false` | Enable built-in wallet connection UI using Reown AppKit. Supports EVM, Bitcoin, and Solana wallets. Requires `walletConnectProjectId`. |
-| `walletConnectProjectId` | `string` | - | WalletConnect project ID for the built-in wallet connection. Get one at . |
-| `defaultReceiveAddress` | `string` | - | Fixed receive address for swaps. When set, users cannot change the receive address. |
+The widget connects wallets through [Reown AppKit](https://reown.com/appkit)
+and provides the `WagmiProvider` / `QueryClient` it needs — you don't wrap it in your own.
-## Theming
+### Initializing AppKit
-The widget supports both simple theme modes and full customization.
+**The widget renders nothing until AppKit is initialized.** There are two ways to satisfy this:
-### Simple Theme Mode
+- **Let the widget initialize AppKit (default).** Pass `walletConnectProjectId` and the widget creates
+ and owns its own AppKit instance. Get a free project ID at .
-```tsx
-
-// or
-
-```
+ ```tsx
+
+ ```
-### Custom Theme Configuration
+- **Reuse your app's existing AppKit.** If your host app already calls `createAppKit()` (with a wagmi
+ EVM adapter), omit `walletConnectProjectId`. The widget detects the shared AppKit singleton, reads the
+ wagmi config off it, and provides its own `WagmiProvider` / `QueryClient` from that config — **you wrap
+ the widget in no providers of your own.** Pair this with `showConnectButton={false}` to drive
+ connection entirely from your own UI. See `src/demo/ExternalWalletApp.tsx` for a full host example.
-```tsx
-import { SwapWidget } from "@shapeshiftoss/swap-widget";
-import type { ThemeConfig } from "@shapeshiftoss/swap-widget";
+ Two requirements for this mode:
-const customTheme: ThemeConfig = {
- mode: "dark",
- accentColor: "#3861fb", // Primary accent color (buttons, focus states)
- backgroundColor: "#0a0a14", // Widget background
- cardColor: "#12121c", // Card/panel background
- textColor: "#ffffff", // Primary text color
- borderRadius: "12px", // Border radius for elements
- fontFamily: "Inter, sans-serif",
-};
+ - **Initialize AppKit _before_ the widget mounts.** The widget reads the AppKit singleton when it
+ mounts and does not poll for late initialization — if AppKit isn't up yet, the widget renders nothing.
+ - **Dedupe the AppKit/wagmi packages.** `@reown/appkit*`, `wagmi`, and `viem` must resolve to a single
+ shared copy in your app, so the widget and your app share one AppKit instance and one wagmi state. A
+ duplicated copy means the widget reads an empty store and shows no connection.
-function App() {
- return ;
-}
-```
+The header shows a built-in **Connect** button by default; set `showConnectButton={false}` to hide it
+and drive connection from your own UI.
-### ThemeConfig Properties
+### Supported wallet namespaces
-| Property | Type | Description |
-| ----------------- | ------------------- | -------------------------------------------------- |
-| `mode` | `"light" \| "dark"` | Base theme mode. Required. |
-| `accentColor` | `string` | Primary accent color for buttons and focus states. |
-| `backgroundColor` | `string` | Widget background color. |
-| `cardColor` | `string` | Card and panel background color. |
-| `textColor` | `string` | Primary text color. |
-| `borderRadius` | `string` | Border radius for UI elements. |
-| `fontFamily` | `string` | Font family for the widget. |
+Once connected, the widget can sign and broadcast transactions for three wallet namespaces:
-## Examples
+| Namespace | Chains | Example wallets |
+| --------- | ------------------------------- | -------------------------------- |
+| `eip155` | All supported EVM chains | MetaMask, WalletConnect, Rabby |
+| `bip122` | Bitcoin and other UTXO chains | WalletConnect-compatible wallets |
+| `solana` | Solana | Phantom, Solflare |
-### Basic Usage
+The header shows a **Connect** button by default (toggle with `showConnectButton`) that opens the
+AppKit modal. Swaps whose sell asset is not in an executable namespace (see
+[Supported Chains](#supported-chains)) redirect to [app.shapeshift.com](https://app.shapeshift.com)
+when `allowShapeshiftRedirect` is enabled.
-```tsx
-import { SwapWidget } from "@shapeshiftoss/swap-widget";
+## Props Reference
-function App() {
- return ;
+### `SwapWidgetProps`
+
+| Prop | Type | Default | Description |
+| ------------------------ | ----------------------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------- |
+| `walletConnectProjectId` | `string` | – | Reown AppKit / WalletConnect project ID. The widget uses it to initialize AppKit. Required unless your host app already initializes AppKit (see [Wallet Connection](#wallet-connection)). |
+| `partnerCode` | `string` | – | Your registered partner code for affiliate fee attribution. See [Partner Codes](#partner-codes--affiliate-revenue). |
+| `apiBaseUrl` | `string` | `https://api.shapeshift.com` | Override the API base URL. Useful for testing or custom deployments. |
+| `defaultSellAsset` | `Asset` | ETH on Ethereum | Initial asset to sell. |
+| `defaultBuyAsset` | `Asset` | USDC on Ethereum | Initial asset to buy. |
+| `sellFilters` | `SwapWidgetFilters` | `{}` | Restrict which chains/assets are selectable for the **sell** side. See [Filtering](#filtering-chains-and-assets). |
+| `buyFilters` | `SwapWidgetFilters` | `{}` | Restrict which chains/assets are selectable for the **buy** side. |
+| `allowedSwapperNames` | `SwapperName[]` | all enabled | Limit quotes to specific swappers. See [Supported Swappers](#supported-swappers). |
+| `allowShapeshiftRedirect`| `boolean` | `true` | When a swap isn't executable in-widget, redirect to app.shapeshift.com instead of hiding it. |
+| `isBuyAssetLocked` | `boolean` | `false` | Prevent the user from changing the buy asset. |
+| `theme` | `ThemeMode \| ThemeConfig` | `"dark"` | Theme mode (`"light"` or `"dark"`) or a full theme configuration object. See [Theming](#theming). |
+| `defaultSlippage` | `string` | `"0.5"` | Default slippage tolerance, as a percentage string. |
+| `showPoweredBy` | `boolean` | `true` | Show the "Powered by ShapeShift" footer. |
+| `showConnectButton` | `boolean` | `true` | Show the built-in Connect button in the widget header. |
+| `ratesRefetchInterval` | `number` | `15000` | How often (ms) to refetch swap rates. |
+| `onSwapSuccess` | `(txHash: string) => void` | – | Called when a swap transaction succeeds. |
+| `onSwapError` | `(error: Error) => void` | – | Called when a swap transaction fails. |
+
+## Filtering Chains and Assets
+
+Restrict the sell and/or buy asset selectors independently via the `sellFilters` and `buyFilters`
+props. Both accept the same shape:
+
+```typescript
+type SwapWidgetFilters = {
+ allowedChainIds?: ChainId[] // If set, only these chains are selectable
+ disabledChainIds?: ChainId[] // Hide these chains
+ allowedAssetIds?: AssetId[] // If set, only these assets are selectable
+ disabledAssetIds?: AssetId[] // Hide these assets
}
```
-### With External Wallet Connection (wagmi/viem)
-
-If you already have wagmi set up in your application, you can pass the wallet client directly:
-
```tsx
-import { SwapWidget } from "@shapeshiftoss/swap-widget";
-import { useWalletClient } from "wagmi";
+import { EVM_CHAIN_IDS, SwapWidget } from '@shapeshiftoss/swap-widget'
function App() {
- const { data: walletClient } = useWalletClient();
-
return (
{
- // Your custom wallet connection logic
- }}
- onSwapSuccess={(txHash) => {
- console.log("Swap successful:", txHash);
- }}
- onSwapError={(error) => {
- console.error("Swap failed:", error);
+ // Only allow selling ETH-chain, Polygon, and Arbitrum assets
+ sellFilters={{
+ allowedChainIds: [EVM_CHAIN_IDS.ethereum, EVM_CHAIN_IDS.polygon, EVM_CHAIN_IDS.arbitrum],
}}
- theme={{
- mode: "dark",
- accentColor: "#3861fb",
- backgroundColor: "#0a0a14",
- cardColor: "#12121c",
+ // Hide a specific buy token
+ buyFilters={{
+ disabledAssetIds: ['eip155:1/erc20:0x...'],
}}
+ theme="dark"
/>
- );
+ )
}
```
-### With Custom Default Assets
+## Theming
+
+The widget supports a simple light/dark mode or a full theme configuration object.
+
+### Simple theme mode
```tsx
-import { SwapWidget } from "@shapeshiftoss/swap-widget";
-import type { Asset } from "@shapeshiftoss/swap-widget";
+
+// or
+
+```
-const defaultSellAsset: Asset = {
- assetId: "eip155:137/slip44:966",
- chainId: "eip155:137",
- symbol: "MATIC",
- name: "Polygon",
- precision: 18,
- icon: "https://example.com/matic.png",
-};
+### Custom theme configuration
-const defaultBuyAsset: Asset = {
- assetId: "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
- chainId: "eip155:137",
- symbol: "USDC",
- name: "USD Coin",
- precision: 6,
- icon: "https://example.com/usdc.png",
-};
+```tsx
+import { SwapWidget } from '@shapeshiftoss/swap-widget'
+import type { ThemeConfig } from '@shapeshiftoss/swap-widget'
+
+const customTheme: ThemeConfig = {
+ mode: 'dark', // required
+ accentColor: '#3861fb',
+ backgroundColor: '#0a0a14',
+ cardColor: '#12121c',
+ textColor: '#ffffff',
+ borderRadius: '12px',
+ fontFamily: 'Inter, sans-serif',
+ buttonVariant: 'filled',
+}
function App() {
- return (
-
- );
+ return
}
```
-### Restricting Available Chains and Assets
+### `ThemeConfig` properties
+
+| Property | Type | Description |
+| -------------------- | ------------------------ | ---------------------------------------------------- |
+| `mode` | `"light" \| "dark"` | Base theme mode. **Required.** |
+| `accentColor` | `string` | Primary accent color (buttons, focus states). |
+| `backgroundColor` | `string` | Widget background color. |
+| `cardColor` | `string` | Card / panel background color. |
+| `textColor` | `string` | Primary text color. |
+| `secondaryTextColor` | `string` | Secondary text color. |
+| `mutedTextColor` | `string` | Muted/tertiary text color. |
+| `inputColor` | `string` | Input field background color. |
+| `hoverColor` | `string` | Hover background color. |
+| `borderColor` | `string` | Border color. |
+| `borderRadius` | `string` | Base border radius for UI elements (e.g. `"12px"`). |
+| `fontFamily` | `string` | Font family for the widget. |
+| `buttonVariant` | `"filled" \| "outline"` | Primary button style. |
-Use `allowedChainIds` to restrict the widget to only show specific chains. This is useful when you want to limit swaps to certain networks.
+## Examples
+
+### Basic usage
```tsx
-import { SwapWidget, EVM_CHAIN_IDS } from "@shapeshiftoss/swap-widget";
+import { SwapWidget } from '@shapeshiftoss/swap-widget'
function App() {
- return (
-
- );
+ return
}
```
-### With Built-in Wallet Connection (Multi-Chain)
-
-The widget can manage wallet connections internally using Reown AppKit, which supports EVM chains, Bitcoin, and Solana. This is useful when you don't have an existing wallet connection setup.
+### Custom default assets
```tsx
-import { SwapWidget } from "@shapeshiftoss/swap-widget";
+import { SwapWidget } from '@shapeshiftoss/swap-widget'
+import type { Asset } from '@shapeshiftoss/swap-widget'
+
+const defaultSellAsset: Asset = {
+ assetId: 'eip155:137/slip44:966',
+ chainId: 'eip155:137',
+ symbol: 'POL',
+ name: 'Polygon',
+ precision: 18,
+}
+
+const defaultBuyAsset: Asset = {
+ assetId: 'eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
+ chainId: 'eip155:137',
+ symbol: 'USDC',
+ name: 'USD Coin',
+ precision: 6,
+}
function App() {
return (
- );
+ )
}
```
-When `enableWalletConnection` is true, the widget will:
-- Show a "Connect" button that opens the AppKit modal
-- Support connecting EVM wallets (MetaMask, WalletConnect, etc.)
-- Support connecting Bitcoin wallets via WalletConnect
-- Support connecting Solana wallets (Phantom, Solflare, etc.)
+### Locking the buy asset
-### With Fixed Receive Address
-
-Use `defaultReceiveAddress` to lock the receive address. When set, users cannot change the destination address. This is useful for integrations where you want all swaps to go to a specific address.
+Use `isBuyAssetLocked` so users can only change the sell side — useful when you want all swaps to
+end in a specific token.
```tsx
-import { SwapWidget } from "@shapeshiftoss/swap-widget";
-
-function App() {
- return (
-
- );
-}
+
```
## Exported Types
@@ -281,246 +303,224 @@ function App() {
import type {
Asset,
AssetId,
- ChainId,
Chain,
- TradeRate,
- TradeQuote,
- SwapperName,
+ ChainId,
+ SwapWidgetFilters,
SwapWidgetProps,
- ThemeMode,
ThemeConfig,
-} from "@shapeshiftoss/swap-widget";
+ ThemeMode,
+ TradeQuote,
+ TradeRate,
+} from '@shapeshiftoss/swap-widget'
```
-### Asset
-
-```typescript
-type Asset = {
- assetId: AssetId; // CAIP-19 format: "eip155:1/slip44:60"
- chainId: ChainId; // CAIP-2 format: "eip155:1"
- symbol: string; // e.g., "ETH"
- name: string; // e.g., "Ethereum"
- precision: number; // e.g., 18
- icon?: string; // URL to asset icon
- color?: string; // Brand color
- networkName?: string; // Display name for the network
- networkIcon?: string; // URL to network icon
- explorer?: string; // Block explorer URL
- explorerTxLink?: string; // Transaction explorer link template
- explorerAddressLink?: string; // Address explorer link template
- relatedAssetKey?: AssetId | null; // Related asset for bridged tokens
-};
-```
+`SwapperName` is exported as a runtime value (an `enum`) — import it from the value position, not as
+a type.
-### SwapperName
+### `Asset`
```typescript
-type SwapperName =
- | "THORChain"
- | "MAYAChain"
- | "CoW Swap"
- | "0x"
- | "Portals"
- | "Chainflip"
- | "Relay"
- | "Bebop"
- | "Jupiter"
- | "1inch"
- | "ButterSwap"
- | "ArbitrumBridge";
-```
-
-### TradeRate
-
-```typescript
-type TradeRate = {
- swapperName: SwapperName;
- rate: string;
- buyAmountCryptoBaseUnit: string;
- sellAmountCryptoBaseUnit: string;
- steps: number;
- estimatedExecutionTimeMs?: number;
- affiliateBps: string;
- networkFeeCryptoBaseUnit?: string;
- error?: {
- code: string;
- message: string;
- };
- id?: string;
-};
+type Asset = {
+ assetId: AssetId // CAIP-19, e.g. "eip155:1/slip44:60"
+ chainId: ChainId // CAIP-2, e.g. "eip155:1"
+ symbol: string // e.g. "ETH"
+ name: string // e.g. "Ethereum"
+ precision: number // e.g. 18
+ icon?: string
+ color?: string
+ networkName?: string
+ networkIcon?: string
+ explorer?: string
+ explorerTxLink?: string
+ explorerAddressLink?: string
+ relatedAssetKey?: AssetId | null
+}
```
## Exported Utilities
```typescript
import {
- isEvmChainId,
- getEvmChainIdNumber,
- getChainType,
- formatAmount,
- parseAmount,
- truncateAddress,
- EVM_CHAIN_IDS,
- UTXO_CHAIN_IDS,
COSMOS_CHAIN_IDS,
+ EVM_CHAIN_IDS,
OTHER_CHAIN_IDS,
- CHAIN_METADATA,
- getChainMeta,
- getChainName,
- getChainIcon,
+ REDIRECT_ONLY_CHAIN_IDS,
+ SwapperName,
+ UTXO_CHAIN_IDS,
+ formatAmount,
+ getBaseAsset,
getChainColor,
-} from "@shapeshiftoss/swap-widget";
+ getChainIcon,
+ getChainName,
+ getChainType,
+ getEvmNetworkId,
+ getExplorerTxLink,
+ isEvmChainId,
+ isWidgetExecutableChainId,
+ isWidgetSupportedChainId,
+ parseAmount,
+ truncateAddress,
+} from '@shapeshiftoss/swap-widget'
```
-### Chain Type Utilities
-
-| Function | Signature | Description |
-| --------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------- |
-| `isEvmChainId` | `(chainId: string) => boolean` | Check if a chain ID is an EVM chain. |
-| `getEvmChainIdNumber` | `(chainId: string) => number` | Extract the numeric chain ID from a CAIP-2 chain ID. |
-| `getChainType` | `(chainId: string) => "evm" \| "utxo" \| "cosmos" \| "solana" \| "other"` | Get the chain type from a chain ID. |
+### Chain helpers
-### Amount Formatting
+| Function | Signature | Description |
+| --------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------ |
+| `isEvmChainId` | `(chainId: string) => boolean` | Whether a chain ID is an EVM chain. |
+| `getEvmNetworkId` | `(chainId: string) => number` | Extract the numeric network ID from a CAIP-2 EVM chain ID. |
+| `getChainType` | `(chainId: string) => "evm" \| "utxo" \| "cosmos" \| "solana" \| "other"` | Classify a chain by namespace. |
+| `isWidgetSupportedChainId` | `(chainId: string) => boolean` | Whether the widget lists assets on this chain. |
+| `isWidgetExecutableChainId` | `(chainId: string) => boolean` | Whether the widget can sign/execute swaps on this chain in-app. |
+| `getChainName` | `(chainId: ChainId) => string` | Display name for a chain. |
+| `getChainIcon` | `(chainId: ChainId) => string \| undefined` | Icon URL for a chain. |
+| `getChainColor` | `(chainId: ChainId) => string` | Brand color for a chain. |
+| `getBaseAsset` | `(chainId: ChainId) => Asset \| undefined` | Native asset for a chain. |
+| `getExplorerTxLink` | `(chainId: ChainId) => string \| undefined` | Block-explorer transaction link template. |
-| Function | Signature | Description |
-| ----------------- | -------------------------------------------------------------------- | -------------------------------------------------------- |
-| `formatAmount` | `(amount: string, decimals: number, maxDecimals?: number) => string` | Format a base unit amount for display. |
-| `parseAmount` | `(amount: string, decimals: number) => string` | Parse a human-readable amount to base units. |
-| `truncateAddress` | `(address: string, chars?: number) => string` | Truncate an address for display (e.g., `0x1234...5678`). |
+### Amount and address formatting
-### Chain Metadata
+| Function | Signature | Description |
+| ----------------- | -------------------------------------------------------------------- | ---------------------------------------------------- |
+| `formatAmount` | `(amount: string, decimals: number, maxDecimals?: number) => string` | Format a base-unit amount for display. |
+| `parseAmount` | `(amount: string, decimals: number) => string` | Parse a human-readable amount into base units. |
+| `truncateAddress` | `(address: string, chars?: number) => string` | Truncate an address (e.g. `0x1234...5678`). |
-| Function | Signature | Description |
-| --------------- | ---------------------------------------------- | --------------------------------- |
-| `getChainMeta` | `(chainId: ChainId) => ChainMeta \| undefined` | Get full metadata for a chain. |
-| `getChainName` | `(chainId: ChainId) => string` | Get the display name for a chain. |
-| `getChainIcon` | `(chainId: ChainId) => string \| undefined` | Get the icon URL for a chain. |
-| `getChainColor` | `(chainId: ChainId) => string` | Get the brand color for a chain. |
-
-### Chain ID Constants
+### Chain ID constants
```typescript
const EVM_CHAIN_IDS = {
- ethereum: "eip155:1",
- arbitrum: "eip155:42161",
- optimism: "eip155:10",
- polygon: "eip155:137",
- base: "eip155:8453",
- avalanche: "eip155:43114",
- bsc: "eip155:56",
- gnosis: "eip155:100",
-};
+ ethereum: 'eip155:1',
+ arbitrum: 'eip155:42161',
+ optimism: 'eip155:10',
+ polygon: 'eip155:137',
+ base: 'eip155:8453',
+ avalanche: 'eip155:43114',
+ bsc: 'eip155:56',
+ gnosis: 'eip155:100',
+ monad: 'eip155:143',
+ megaEth: 'eip155:4326',
+ hyperEvm: 'eip155:999',
+ plasma: 'eip155:9745',
+ katana: 'eip155:747474',
+}
const UTXO_CHAIN_IDS = {
- bitcoin: "bip122:000000000019d6689c085ae165831e93",
- bitcoinCash: "bip122:000000000000000000651ef99cb9fcbe",
- dogecoin: "bip122:00000000001a91e3dace36e2be3bf030",
- litecoin: "bip122:12a765e31ffd4059bada1e25190f6e98",
-};
+ bitcoin: 'bip122:000000000019d6689c085ae165831e93',
+ bitcoinCash: 'bip122:000000000000000000651ef99cb9fcbe',
+ dogecoin: 'bip122:00000000001a91e3dace36e2be3bf030',
+ litecoin: 'bip122:12a765e31ffd4059bada1e25190f6e98',
+}
const COSMOS_CHAIN_IDS = {
- cosmos: "cosmos:cosmoshub-4",
- thorchain: "cosmos:thorchain-1",
- mayachain: "cosmos:mayachain-mainnet-v1",
-};
+ cosmos: 'cosmos:cosmoshub-4',
+ thorchain: 'cosmos:thorchain-1',
+ mayachain: 'cosmos:mayachain-mainnet-v1',
+}
const OTHER_CHAIN_IDS = {
- solana: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
-};
+ solana: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
+}
+
+// Listed in the asset selector, but swaps redirect to app.shapeshift.com (not executed in-widget)
+const REDIRECT_ONLY_CHAIN_IDS = {
+ zcash: 'bip122:00040fe8ec8471911baa1db1266ea15d',
+ tron: 'tron:0x2b6653dc',
+ sui: 'sui:35834a8a',
+ ton: 'ton:mainnet',
+ near: 'near:mainnet',
+ starknet: 'starknet:SN_MAIN',
+}
```
## Exported Hooks
```typescript
import {
- useAssets,
useAssetById,
- useChains,
- useAssetsByChainId,
useAssetSearch,
-} from "@shapeshiftoss/swap-widget";
+ useAssets,
+ useAssetsByChainId,
+ useChains,
+} from '@shapeshiftoss/swap-widget'
```
-| Hook | Return Type | Description |
-| --------------------------------- | ------------------------------------------ | -------------------------------------------------------------- |
-| `useAssets()` | `{ data: Asset[], isLoading, error, ... }` | Fetch all available assets. |
-| `useAssetById(assetId)` | `{ data: Asset \| undefined, ... }` | Fetch a specific asset by ID. |
-| `useChains()` | `{ data: ChainInfo[], ... }` | Fetch all available chains with their native assets. |
-| `useAssetsByChainId(chainId)` | `{ data: Asset[], ... }` | Fetch all assets for a specific chain. |
-| `useAssetSearch(query, chainId?)` | `{ data: Asset[], ... }` | Search assets by symbol or name, optionally filtered by chain. |
-
-## Supported Chains
-
-| Chain | Chain ID | Type |
-| ----------------- | ----------------------------------------- | ------ |
-| Ethereum | `eip155:1` | EVM |
-| Arbitrum One | `eip155:42161` | EVM |
-| Optimism | `eip155:10` | EVM |
-| Polygon | `eip155:137` | EVM |
-| Base | `eip155:8453` | EVM |
-| Avalanche C-Chain | `eip155:43114` | EVM |
-| BNB Smart Chain | `eip155:56` | EVM |
-| Gnosis | `eip155:100` | EVM |
-| Bitcoin | `bip122:000000000019d6689c085ae165831e93` | UTXO |
-| Bitcoin Cash | `bip122:000000000000000000651ef99cb9fcbe` | UTXO |
-| Dogecoin | `bip122:00000000001a91e3dace36e2be3bf030` | UTXO |
-| Litecoin | `bip122:12a765e31ffd4059bada1e25190f6e98` | UTXO |
-| Cosmos Hub | `cosmos:cosmoshub-4` | Cosmos |
-| THORChain | `cosmos:thorchain-1` | Cosmos |
-| MAYAChain | `cosmos:mayachain-mainnet-v1` | Cosmos |
-| Solana | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | Solana |
-
-## Notes and Limitations
-
-### Multi-Chain Swap Support
+| Hook | Description |
+| --------------------------------- | -------------------------------------------------------------- |
+| `useAssets()` | Fetch all available assets. |
+| `useAssetById(assetId)` | Fetch a single asset by ID. |
+| `useChains()` | Fetch all available chains with their native assets. |
+| `useAssetsByChainId(chainId)` | Fetch all assets for a specific chain. |
+| `useAssetSearch(query, chainId?)` | Search assets by symbol or name, optionally filtered by chain. |
-The widget supports swaps across multiple blockchain types:
+These hooks must be used within a mounted `` tree (they rely on the widget's internal
+React Query client).
-- **EVM swaps** (e.g., ETH to USDC, MATIC to WETH) can be executed directly within the widget when a wallet is connected via the `walletClient` prop or through the built-in AppKit wallet connection.
-- **Bitcoin/UTXO swaps** - When using the built-in wallet connection (`enableWalletConnection={true}`), Bitcoin and other UTXO chains can be signed directly via WalletConnect-compatible wallets.
-- **Solana swaps** - Solana transactions can be signed via Phantom, Solflare, or other Solana wallets when using the built-in wallet connection.
-- **Unsupported chains** - Swaps involving chains without wallet support will redirect to [app.shapeshift.com](https://app.shapeshift.com) to complete the transaction.
-
-### Partner Codes
+## Supported Chains
-Register an affiliate account at the affiliate dashboard and claim a partner code (e.g. `vultisig`, `venice`). Pass it via the `partnerCode` prop on the widget, or the `X-Partner-Code` header when using the REST API directly:
+Assets on the following chains appear in the selector. Swaps are **executed in-widget** only for EVM,
+UTXO, and Solana assets (`isWidgetExecutableChainId` returns `true`). Cosmos-SDK and redirect-only
+chains are selectable but route the user to [app.shapeshift.com](https://app.shapeshift.com) to
+complete the swap (when `allowShapeshiftRedirect` is enabled).
+
+| Chain | Chain ID | Type | Executable in-widget |
+| ----------------- | ----------------------------------------- | ------ | -------------------- |
+| Ethereum | `eip155:1` | EVM | ✅ |
+| Arbitrum One | `eip155:42161` | EVM | ✅ |
+| Optimism | `eip155:10` | EVM | ✅ |
+| Polygon | `eip155:137` | EVM | ✅ |
+| Base | `eip155:8453` | EVM | ✅ |
+| Avalanche C-Chain | `eip155:43114` | EVM | ✅ |
+| BNB Smart Chain | `eip155:56` | EVM | ✅ |
+| Gnosis | `eip155:100` | EVM | ✅ |
+| Monad | `eip155:143` | EVM | ✅ |
+| MegaETH | `eip155:4326` | EVM | ✅ |
+| HyperEVM | `eip155:999` | EVM | ✅ |
+| Plasma | `eip155:9745` | EVM | ✅ |
+| Katana | `eip155:747474` | EVM | ✅ |
+| Bitcoin | `bip122:000000000019d6689c085ae165831e93` | UTXO | ✅ |
+| Bitcoin Cash | `bip122:000000000000000000651ef99cb9fcbe` | UTXO | ✅ |
+| Dogecoin | `bip122:00000000001a91e3dace36e2be3bf030` | UTXO | ✅ |
+| Litecoin | `bip122:12a765e31ffd4059bada1e25190f6e98` | UTXO | ✅ |
+| Solana | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | Solana | ✅ |
+| Cosmos Hub | `cosmos:cosmoshub-4` | Cosmos | ↗ redirect |
+| THORChain | `cosmos:thorchain-1` | Cosmos | ↗ redirect |
+| MAYAChain | `cosmos:mayachain-mainnet-v1` | Cosmos | ↗ redirect |
+| Zcash, Tron, Sui, TON, NEAR, Starknet | _see `REDIRECT_ONLY_CHAIN_IDS`_ | Other | ↗ redirect |
+
+## Supported Swappers
+
+The widget aggregates quotes across the protocols below and surfaces the best rate. Use
+`allowedSwapperNames` to restrict which are used.
+
+- **NEAR Intents** (`SwapperName.NearIntents`)
+- **Relay** (`SwapperName.Relay`)
+- **THORChain** (`SwapperName.Thorchain`)
+- **MAYAChain** (`SwapperName.Mayachain`)
+
+> The set of enabled swappers changes over time. Treat this list as current-at-publish; the
+> authoritative source is the `SwapperName` enum exported by this package.
+
+## Partner Codes & Affiliate Revenue
+
+Pass your registered `partnerCode` to attribute swaps to your affiliate account and earn revenue
+share. The widget forwards it to the ShapeShift Public API as the `X-Partner-Code` header, and the
+API applies your configured fee automatically.
+```tsx
+
```
-GET /v1/swap/rates?...
-X-Partner-Code: your-partner-code
-```
-
-The API resolves the partner code to the registered affiliate address and BPS automatically.
-### Internal QueryClient
+See the [Affiliate Program guide](../../docs/affiliates.md) for how to obtain a partner code and how
+revenue attribution works.
-The widget manages its own React Query `QueryClient` internally. You do not need to wrap it in a `QueryClientProvider`.
-
-### Swap Aggregation
-
-The widget fetches quotes from multiple DEXs and aggregators including:
-
-- THORChain
-- MAYAChain
-- CoW Swap
-- 0x
-- 1inch
-- Portals
-- Chainflip
-- Jupiter (Solana)
-- Bebop
-- Relay
-- ButterSwap
-- Arbitrum Bridge
-
-### Wallet Balance Display
-
-When a wallet is connected (`walletClient` prop), the widget displays the user's balance for the selected sell and buy assets. This only works for EVM chains where the connected wallet has assets.
-
-### USD Price Display
-
-The widget automatically fetches and displays USD prices for selected assets.
-
-### Mobile Responsive
+## Notes and Limitations
-The widget is designed to be responsive and works well on mobile devices.
+- **Self-contained providers.** The widget renders its own `WagmiProvider` and React Query
+ `QueryClient`. Don't wrap it in your own — and remember it renders nothing until AppKit is
+ initialized, whether by `walletConnectProjectId` or by your host app (see [Wallet Connection](#wallet-connection)).
+- **Balances and USD prices.** When a wallet is connected, the widget shows balances and USD prices
+ for the selected assets.
+- **Redirects.** Assets on non-executable chains (Cosmos, Zcash, Tron, Sui, TON, NEAR, Starknet)
+ send the user to app.shapeshift.com to finish the swap, unless `allowShapeshiftRedirect={false}`.
+- **Mobile responsive.** The widget is designed to work on mobile as well as desktop.
diff --git a/packages/swap-widget/package.json b/packages/swap-widget/package.json
index 757e641b7cc..63acc309628 100644
--- a/packages/swap-widget/package.json
+++ b/packages/swap-widget/package.json
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/swap-widget",
- "version": "0.4.0",
+ "version": "0.5.0",
"description": "Embeddable swap widget using ShapeShift API",
"repository": "https://github.com/shapeshift/web",
"license": "MIT",
diff --git a/packages/swap-widget/src/components/SwapWidget.tsx b/packages/swap-widget/src/components/SwapWidget.tsx
index cd617d75db4..180802c5d8b 100644
--- a/packages/swap-widget/src/components/SwapWidget.tsx
+++ b/packages/swap-widget/src/components/SwapWidget.tsx
@@ -42,6 +42,7 @@ type SwapWidgetContentProps = {
sellFilters: SwapWidgetFilters
buyFilters: SwapWidgetFilters
allowedSwapperNames?: SwapWidgetProps['allowedSwapperNames']
+ ratesRefetchInterval?: SwapWidgetProps['ratesRefetchInterval']
}
const SwapWidgetContent = ({
@@ -57,6 +58,7 @@ const SwapWidgetContent = ({
sellFilters,
buyFilters,
allowedSwapperNames,
+ ratesRefetchInterval,
}: SwapWidgetContentProps) => {
const state = SwapMachineCtx.useSelector(s => s)
@@ -66,7 +68,11 @@ const SwapWidgetContent = ({
const themeMode: ThemeMode = typeof theme === 'string' ? theme : theme.mode
const themeConfig = typeof theme === 'object' ? theme : undefined
- const displayValues = useSwapDisplayValues({ apiClient, allowedSwapperNames })
+ const displayValues = useSwapDisplayValues({
+ apiClient,
+ allowedSwapperNames,
+ ratesRefetchInterval,
+ })
const { rates, sellAssetBalance, refetchSellBalance, refetchBuyBalance } = displayValues
const {
@@ -247,6 +253,7 @@ type SwapWidgetCoreProps = {
sellFilters: SwapWidgetFilters
buyFilters: SwapWidgetFilters
allowedSwapperNames?: SwapWidgetProps['allowedSwapperNames']
+ ratesRefetchInterval?: SwapWidgetProps['ratesRefetchInterval']
}
const SwapWidgetCore = ({
@@ -265,6 +272,7 @@ const SwapWidgetCore = ({
sellFilters,
buyFilters,
allowedSwapperNames,
+ ratesRefetchInterval,
}: SwapWidgetCoreProps) => {
const actorRef = SwapMachineCtx.useActorRef()
@@ -383,6 +391,7 @@ const SwapWidgetCore = ({
sellFilters={sellFilters}
buyFilters={buyFilters}
allowedSwapperNames={allowedSwapperNames}
+ ratesRefetchInterval={ratesRefetchInterval}
/>
)
@@ -417,6 +426,7 @@ export const SwapWidget = (props: SwapWidgetProps) => {
sellFilters={props.sellFilters ?? {}}
buyFilters={props.buyFilters ?? {}}
allowedSwapperNames={props.allowedSwapperNames}
+ ratesRefetchInterval={props.ratesRefetchInterval}
/>
diff --git a/packages/swap-widget/src/components/WalletProvider.tsx b/packages/swap-widget/src/components/WalletProvider.tsx
index bb8f2b740e9..e11628476ff 100644
--- a/packages/swap-widget/src/components/WalletProvider.tsx
+++ b/packages/swap-widget/src/components/WalletProvider.tsx
@@ -1,11 +1,11 @@
import { useAppKit } from '@reown/appkit/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import type { ReactNode } from 'react'
-import { useCallback, useEffect, useMemo, useState } from 'react'
+import { useCallback, useEffect, useState } from 'react'
import type { Config } from 'wagmi'
import { WagmiProvider } from 'wagmi'
-import { getWagmiAdapter, initializeAppKit, isAppKitInitialized } from '../config/appkit'
+import { getActiveWagmiConfig, initializeAppKit, isAppKitInitialized } from '../config/appkit'
import { useSwapWallet } from '../contexts/SwapWalletContext'
import { truncateAddress } from '../types'
@@ -17,20 +17,24 @@ type AppKitWalletProviderProps = {
}
export const AppKitWalletProvider = ({ projectId, children }: AppKitWalletProviderProps) => {
- const [isReady, setIsReady] = useState(false)
+ // Seed from the SDK singleton so a host-initialized AppKit is picked up on the
+ // first render (no flash of null) when it was created before the widget mounts.
+ const [wagmiConfig, setWagmiConfig] = useState(() =>
+ isAppKitInitialized() ? getActiveWagmiConfig() : undefined,
+ )
useEffect(() => {
- if (projectId) initializeAppKit(projectId)
- if (isAppKitInitialized()) setIsReady(true)
+ // Self-init only when no AppKit exists yet. If the host already called
+ // createAppKit, we hook into that shared instance rather than creating a second.
+ if (projectId && !isAppKitInitialized()) initializeAppKit(projectId)
+ if (isAppKitInitialized()) setWagmiConfig(getActiveWagmiConfig())
}, [projectId])
- const wagmiConfig = useMemo((): Config | undefined => {
- if (!isReady) return undefined
- return getWagmiAdapter()?.wagmiConfig as unknown as Config | undefined
- }, [isReady])
-
if (!wagmiConfig) return null
+ // We always own the WagmiProvider / QueryClient — built from the wagmi config we
+ // read off the shared AppKit singleton (whether self-init or host-owned). This is
+ // what lets a host integrate by calling createAppKit() alone, wrapping nothing.
return (
{children}
diff --git a/packages/swap-widget/src/config/appkit.ts b/packages/swap-widget/src/config/appkit.ts
index 7df859965b6..f52b39d7b62 100644
--- a/packages/swap-widget/src/config/appkit.ts
+++ b/packages/swap-widget/src/config/appkit.ts
@@ -16,11 +16,12 @@ import {
polygon,
solana,
} from '@reown/appkit/networks'
-import { createAppKit } from '@reown/appkit/react'
+import { createAppKit, modal } from '@reown/appkit/react'
import { BitcoinAdapter } from '@reown/appkit-adapter-bitcoin'
import { SolanaAdapter } from '@reown/appkit-adapter-solana/react'
import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'
import { PhantomWalletAdapter, SolflareWalletAdapter } from '@solana/wallet-adapter-wallets'
+import type { Config } from 'wagmi'
const EVM_NETWORKS: readonly AppKitNetwork[] = [
mainnet,
@@ -40,17 +41,28 @@ const EVM_NETWORKS: readonly AppKitNetwork[] = [
const ALL_NETWORKS: readonly AppKitNetwork[] = [...EVM_NETWORKS, bitcoin, solana]
-let wagmiAdapter: WagmiAdapter | null = null
-let appKitInitialized = false
+// AppKit keeps a module-level `modal` singleton, set by `createAppKit` whether
+// it's called by this widget (self-init) or by the host app. Reading it lets the
+// widget detect — and hook into — an AppKit the host already initialized, as long
+// as `@reown/appkit*` is deduped to a single shared copy in the consumer's tree.
+export const isAppKitInitialized = (): boolean => modal !== undefined
-export const getWagmiAdapter = (): WagmiAdapter | null => wagmiAdapter
-
-export const isAppKitInitialized = (): boolean => appKitInitialized
+// The active wagmi Config, read off the AppKit singleton's EVM adapter. Works in
+// both modes — self-init (we registered the adapter via createAppKit) and
+// host-owned (the host's adapter). Because the widget can pull the shared config
+// from the singleton, it builds its own WagmiProvider from it — so a host only
+// needs to call `createAppKit()`, with no WagmiProvider/QueryClient wrapping.
+export const getActiveWagmiConfig = (): Config | undefined => {
+ const evmAdapter = modal?.chainAdapters?.eip155 as WagmiAdapter | undefined
+ return evmAdapter?.wagmiConfig
+}
export const initializeAppKit = (projectId: string): void => {
- if (appKitInitialized) return
+ // Already initialized — either by a previous call here, or by the host app. In
+ // the latter case we skip entirely and reuse the host's AppKit + wagmi config.
+ if (modal) return
- wagmiAdapter = new WagmiAdapter({
+ const wagmiAdapter = new WagmiAdapter({
networks: [...EVM_NETWORKS],
projectId,
})
@@ -68,6 +80,4 @@ export const initializeAppKit = (projectId: string): void => {
send: false,
},
})
-
- appKitInitialized = true
}
diff --git a/packages/swap-widget/src/demo/ExternalWalletApp.tsx b/packages/swap-widget/src/demo/ExternalWalletApp.tsx
index 8b090dc74e2..dd2c42ec65e 100644
--- a/packages/swap-widget/src/demo/ExternalWalletApp.tsx
+++ b/packages/swap-widget/src/demo/ExternalWalletApp.tsx
@@ -1,10 +1,31 @@
import './App.css'
-import { useAppKit, useAppKitAccount } from '@reown/appkit/react'
+import type { AppKitNetwork } from '@reown/appkit/networks'
+import {
+ arbitrum,
+ avalanche,
+ base,
+ bitcoin,
+ bsc,
+ gnosis,
+ hyperEvm,
+ katana,
+ mainnet,
+ megaeth,
+ monad,
+ optimism,
+ plasma,
+ polygon,
+ solana,
+} from '@reown/appkit/networks'
+import { createAppKit, useAppKit, useAppKitAccount } from '@reown/appkit/react'
+import { BitcoinAdapter } from '@reown/appkit-adapter-bitcoin'
+import { SolanaAdapter } from '@reown/appkit-adapter-solana/react'
+import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'
+import { PhantomWalletAdapter, SolflareWalletAdapter } from '@solana/wallet-adapter-wallets'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { SwapWidget } from '../components/SwapWidget'
-import { initializeAppKit } from '../config/appkit'
import { truncateAddress } from '../types'
import { DemoCustomizer, useDemoTheme } from './DemoCustomizer'
import { WidgetModal } from './WidgetModal'
@@ -180,9 +201,54 @@ const loadThemeMode = (): 'light' | 'dark' => {
}
}
+const EVM_NETWORKS: readonly AppKitNetwork[] = [
+ mainnet,
+ polygon,
+ arbitrum,
+ optimism,
+ base,
+ avalanche,
+ bsc,
+ gnosis,
+ monad,
+ megaeth,
+ hyperEvm,
+ plasma,
+ katana,
+]
+
+const ALL_NETWORKS: readonly AppKitNetwork[] = [...EVM_NETWORKS, bitcoin, solana]
+
+let appKitInitialized = false
+
+const initAppKit = (projectId: string): void => {
+ if (appKitInitialized) return
+ appKitInitialized = true
+
+ const wagmiAdapter = new WagmiAdapter({
+ networks: [...EVM_NETWORKS],
+ projectId,
+ })
+
+ const bitcoinAdapter = new BitcoinAdapter()
+ const solanaAdapter = new SolanaAdapter({
+ wallets: [new PhantomWalletAdapter(), new SolflareWalletAdapter()] as any,
+ })
+
+ createAppKit({
+ adapters: [wagmiAdapter, bitcoinAdapter, solanaAdapter],
+ projectId,
+ networks: [...ALL_NETWORKS] as [AppKitNetwork, ...AppKitNetwork[]],
+ features: {
+ send: false,
+ },
+ })
+}
+
export const ExternalWalletApp = () => {
const [theme, setTheme] = useState<'light' | 'dark'>(loadThemeMode)
- const [isReady, setIsReady] = useState(false)
+
+ useState(() => initAppKit(PROJECT_ID))
useEffect(() => {
try {
@@ -192,11 +258,5 @@ export const ExternalWalletApp = () => {
}
}, [theme])
- useEffect(() => {
- initializeAppKit(PROJECT_ID)
- setIsReady(true)
- }, [])
-
- if (!isReady) return null
return
}
diff --git a/packages/swap-widget/src/hooks/useSwapDisplayValues.ts b/packages/swap-widget/src/hooks/useSwapDisplayValues.ts
index 99497a2d8e0..ca1a60783b2 100644
--- a/packages/swap-widget/src/hooks/useSwapDisplayValues.ts
+++ b/packages/swap-widget/src/hooks/useSwapDisplayValues.ts
@@ -18,6 +18,7 @@ import { useSwapRates } from './useSwapRates'
type UseSwapDisplayValuesParams = {
apiClient: ApiClient
allowedSwapperNames?: SwapperName[]
+ ratesRefetchInterval?: number
}
export type SwapDisplayValues = {
@@ -47,6 +48,7 @@ export type SwapDisplayValues = {
export const useSwapDisplayValues = ({
apiClient,
allowedSwapperNames,
+ ratesRefetchInterval,
}: UseSwapDisplayValuesParams): SwapDisplayValues => {
const sellAsset = SwapMachineCtx.useSelector(s => s.context.sellAsset)
const buyAsset = SwapMachineCtx.useSelector(s => s.context.buyAsset)
@@ -72,6 +74,7 @@ export const useSwapDisplayValues = ({
buyAssetId: buyAsset.assetId,
sellAmountCryptoBaseUnit: sellAmountBaseUnit,
allowedSwapperNames,
+ refetchInterval: ratesRefetchInterval,
enabled:
!!sellAmountBaseUnit &&
sellAmountBaseUnit !== '0' &&
diff --git a/packages/swapper/package.json b/packages/swapper/package.json
index c2fb38a40fe..a3802d1af4f 100644
--- a/packages/swapper/package.json
+++ b/packages/swapper/package.json
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/swapper",
- "version": "17.7.3",
+ "version": "17.8.0",
"repository": "https://github.com/shapeshift/web",
"license": "MIT",
"type": "module",
diff --git a/packages/types/package.json b/packages/types/package.json
index 360eb29ced4..d6dd2b1cac7 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/types",
- "version": "8.6.7",
+ "version": "8.6.8",
"description": "Common types shared across packages",
"repository": "https://github.com/shapeshift/web",
"license": "MIT",
diff --git a/packages/unchained-client/package.json b/packages/unchained-client/package.json
index b6d20a3930a..5d07f18a1e3 100644
--- a/packages/unchained-client/package.json
+++ b/packages/unchained-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/unchained-client",
- "version": "10.14.10",
+ "version": "10.14.11",
"repository": "https://github.com/shapeshift/web",
"license": "MIT",
"type": "module",
diff --git a/packages/utils/package.json b/packages/utils/package.json
index 45115752c37..c471d627d87 100644
--- a/packages/utils/package.json
+++ b/packages/utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/utils",
- "version": "1.0.5",
+ "version": "1.1.0",
"repository": "https://github.com/shapeshift/web",
"license": "MIT",
"type": "module",
diff --git a/src/Routes/RoutesCommon.tsx b/src/Routes/RoutesCommon.tsx
index 9fffb7aa82a..20e9854772b 100644
--- a/src/Routes/RoutesCommon.tsx
+++ b/src/Routes/RoutesCommon.tsx
@@ -48,6 +48,16 @@ const Dashboard = makeSuspenseful(
true,
)
+const Foxy = makeSuspenseful(
+ lazy(() =>
+ import('@/pages/Foxy/Foxy').then(({ Foxy }) => ({
+ default: Foxy,
+ })),
+ ),
+ {},
+ true,
+)
+
const Asset = makeSuspenseful(
lazy(() =>
import('@/pages/Assets/Asset').then(({ Asset }) => ({
@@ -420,6 +430,11 @@ export const routes: Route[] = [
main: WalletConnectDeepLink,
hide: true,
},
+ {
+ path: '/foxy',
+ main: Foxy,
+ hide: true,
+ },
{
path: '/limit/*',
label: '',
diff --git a/src/assets/translations/de/main.json b/src/assets/translations/de/main.json
index a07a48053f8..8bbbbe1f817 100644
--- a/src/assets/translations/de/main.json
+++ b/src/assets/translations/de/main.json
@@ -581,13 +581,9 @@
"emptyWithdraws": "Ausstehende Stake Beendigungs-Anfragen werden hier angezeigt.",
"availableDate": "verfügbar %{date}"
},
- "foxyOverview": {
+ "foxFarmingOverview": {
"withdrawals": "Abhebungen",
"emptyWithdraws": "Ausstehende oder aktive Auszahlungen werden hier angezeigt.",
- "availableDate": "Verfügbar %{date}",
- "disabledTitle": "FOXy Einzahlungen sind deaktiviert"
- },
- "foxFarmingOverview": {
"header": "Zahlen Sie ETH-FOX LP Tokens bei %{opportunity} ein, um zu verdienen",
"body": "Genehmigen und Staken Sie Ihre Liquiditätstoken, um Ihre FOX Bonusprämien zu erhalten.",
"rewards": "Die Belohnungen, die Sie verdienen, fallen automatisch an.",
@@ -857,12 +853,6 @@
"swap": "Handel",
"swapRefund": "Swap Rückerstattung"
},
- "foxy": {
- "stake": "Stake",
- "unstake": "Staking beenden",
- "instantUnstake": "Staking sofort beenden",
- "claimWithdraw": "Abhebung anfordern"
- },
"erc20": {
"approve": "Genehmigen",
"approveSymbol": "Genehmige %{symbol}",
diff --git a/src/assets/translations/en/main.json b/src/assets/translations/en/main.json
index 5f01cfdd09a..f0766ee6162 100644
--- a/src/assets/translations/en/main.json
+++ b/src/assets/translations/en/main.json
@@ -581,13 +581,9 @@
"emptyWithdraws": "Pending unstaking requests will show here.",
"availableDate": "available %{date}"
},
- "foxyOverview": {
+ "foxFarmingOverview": {
"withdrawals": "Withdrawals",
"emptyWithdraws": "Pending or active withdrawals will show here.",
- "availableDate": "available %{date}",
- "disabledTitle": "FOXy deposits are disabled"
- },
- "foxFarmingOverview": {
"header": "Deposit ETH-FOX LP tokens to %{opportunity} to earn",
"body": "Approve and stake your liquidity tokens to earn bonus FOX rewards.",
"rewards": "The rewards you earn will accrue automatically.",
@@ -857,12 +853,6 @@
"swap": "Trade",
"swapRefund": "Swap Refund"
},
- "foxy": {
- "stake": "Stake",
- "unstake": "Unstake",
- "instantUnstake": "Instant Unstake",
- "claimWithdraw": "Claim Withdraw"
- },
"erc20": {
"approve": "Approve",
"approveSymbol": "Approve %{symbol}",
diff --git a/src/assets/translations/es/main.json b/src/assets/translations/es/main.json
index 6102b148649..0a0a9960ea8 100644
--- a/src/assets/translations/es/main.json
+++ b/src/assets/translations/es/main.json
@@ -581,13 +581,9 @@
"emptyWithdraws": "Aquí se mostrarán las solicitudes pendientes de retirar Staking.",
"availableDate": "disponible %{date}"
},
- "foxyOverview": {
+ "foxFarmingOverview": {
"withdrawals": "Retiros",
"emptyWithdraws": "Los retiros pendientes o activos se mostrarán aquí.",
- "availableDate": "disponible %{date}",
- "disabledTitle": "Depósitos FOXy inhabilitados"
- },
- "foxFarmingOverview": {
"header": "Deposite tokens LP ETH-FOX en %{opportunity} para ganar",
"body": "Aprueba y Stake tus tokens de liquidez para ganar recompensas adicionales en FOX.",
"rewards": "Las recompensas se acumulan automáticamente.",
@@ -857,12 +853,6 @@
"swap": "Intercambio",
"swapRefund": "Reembolso de Intercambio"
},
- "foxy": {
- "stake": "Stake",
- "unstake": "Retirar de Stake",
- "instantUnstake": "Retirar de Stake al instante",
- "claimWithdraw": "Retirar monto a reclamar"
- },
"erc20": {
"approve": "Aprobar",
"approveSymbol": "Aprobar %{symbol}",
diff --git a/src/assets/translations/fr/main.json b/src/assets/translations/fr/main.json
index 355e4c10e9c..970065142e1 100644
--- a/src/assets/translations/fr/main.json
+++ b/src/assets/translations/fr/main.json
@@ -581,13 +581,9 @@
"emptyWithdraws": "Les demandes de déstaking en attente s'affichent ici.",
"availableDate": "disponible %{date}"
},
- "foxyOverview": {
+ "foxFarmingOverview": {
"withdrawals": "Retraits",
"emptyWithdraws": "Les retraits en attente ou actifs s'afficheront ici.",
- "availableDate": "disponible %{date}",
- "disabledTitle": "Les dépôts FOXy sont désactivés"
- },
- "foxFarmingOverview": {
"header": "Déposer des jetons LP ETH-FOX dans %{opportunity} pour gagner",
"body": "Autorisez et stakez vos jetons de liquidité pour gagner des récompenses bonus en FOX.",
"rewards": "Les récompenses que vous gagnez s'accumuleront automatiquement.",
@@ -857,12 +853,6 @@
"swap": "Échange",
"swapRefund": "Remboursement d'échange"
},
- "foxy": {
- "stake": "Stake",
- "unstake": "Déstake",
- "instantUnstake": "Déstake instantané",
- "claimWithdraw": "Réclamation de retrait"
- },
"erc20": {
"approve": "Autorisation",
"approveSymbol": "Autorisation %{symbol}",
diff --git a/src/assets/translations/glossary.json b/src/assets/translations/glossary.json
index d79b5e2908c..2bb2d12b085 100644
--- a/src/assets/translations/glossary.json
+++ b/src/assets/translations/glossary.json
@@ -59,7 +59,6 @@
"CowSwap": null,
"Jupiter": null,
"Chainflip": null,
- "FOXy": null,
"stake": {
"de": "Stake",
"es": "stake",
diff --git a/src/assets/translations/id/main.json b/src/assets/translations/id/main.json
index f7c2e53c306..aebec99dc56 100644
--- a/src/assets/translations/id/main.json
+++ b/src/assets/translations/id/main.json
@@ -143,16 +143,6 @@
"learnMore": "Pelajari Lebih Lanjut "
}
},
- "foxyOverview": {
- "header": "Setor FOX untuk mendapatkan FOXy untuk menghasilkan",
- "body": "FOXy adalah token rebasing yang dipatok dengan harga FOX. Staked FOX bekerja di berbagai protokol DeFi untuk mendapatkan hasil terbaik.",
- "rewards": "Imbalan yang Anda peroleh akan bertambah secara otomatis.",
- "cta": "Setor FOX Sekarang ",
- "foxyBalance": "Saldo FOXy",
- "withdrawals": "Penarikan",
- "emptyWithdraws": "Penarikan tertunda atau aktif akan ditampilkan di sini.",
- "availableDate": "tersedia %{date}"
- },
"claim": {
"rewardDepositInfo": "Hadiah Anda akan disimpan ke dompet utama Anda.",
"title": "Klaim Hadiah Staking",
@@ -301,12 +291,6 @@
},
"thor": {
"transferOut": "Transfer keluar"
- },
- "foxy": {
- "stake": "Stake",
- "unstake": "Unstake",
- "instantUnstake": "Unstake Instan",
- "claimWithdraw": "Klaim Penarikan"
}
},
"unknown": "Transaksi"
diff --git a/src/assets/translations/ja/main.json b/src/assets/translations/ja/main.json
index f9af44b10bc..2279ce95d76 100644
--- a/src/assets/translations/ja/main.json
+++ b/src/assets/translations/ja/main.json
@@ -581,13 +581,9 @@
"emptyWithdraws": "保留中のステーキング解除のリクエストはここに表示されます。",
"availableDate": "利用可能 %{date}"
},
- "foxyOverview": {
+ "foxFarmingOverview": {
"withdrawals": "出金",
"emptyWithdraws": "保留中の出金またはアクティブな出金はここに表示されます。",
- "availableDate": "利用可能 %{date}",
- "disabledTitle": "FOXyデポジットは無効になっています"
- },
- "foxFarmingOverview": {
"header": "ETH-FOX LP トークンを %{opportunity} に入金して獲得する",
"body": "流動性トークンを承認してステーキングして、ボーナスFOX報酬を獲得する",
"rewards": "獲得した報酬は自動的に蓄積されます。",
@@ -857,12 +853,6 @@
"swap": "トレード",
"swapRefund": "スワップ払い戻し"
},
- "foxy": {
- "stake": "ステーク",
- "unstake": "ステークを解除する",
- "instantUnstake": "一時的にステークを解除する",
- "claimWithdraw": "出金の獲得"
- },
"erc20": {
"approve": "承認する",
"approveSymbol": "%{symbol}を承認",
diff --git a/src/assets/translations/ko/main.json b/src/assets/translations/ko/main.json
index 43ef30f4be6..6735ca56cf5 100644
--- a/src/assets/translations/ko/main.json
+++ b/src/assets/translations/ko/main.json
@@ -144,16 +144,6 @@
"learnMore": "더 알아보기"
}
},
- "foxyOverview": {
- "header": "FOXy를 적립하려면 FOX를 입금하세요.",
- "body": "FOXy는 FOX 가격에 페깅되어 있는 리베이스 토큰입니다. 스테이크된 FOX는 다양한 DeFi 프로토콜에 연동되어 가능한 한 최고의 수익을 얻을 수 있게 합니다.",
- "rewards": "획득한 보상은 자동으로 적립됩니다.",
- "cta": "FOX 입금하기",
- "foxyBalance": "FOXy 밸런스",
- "withdrawals": "출금",
- "emptyWithdraws": "보류 중이거나 활성인 출금이 여기에 표시됩니다.",
- "availableDate": "가능한 날짜 %{date}"
- },
"claim": {
"rewardDepositInfo": "보상은 기본 지갑에 입금됩니다.",
"title": "스테이킹 보상 청구",
@@ -298,12 +288,6 @@
"thor": {
"transferOut": "출금"
},
- "foxy": {
- "stake": "스테이크",
- "unstake": "언스테이크",
- "instantUnstake": "즉시 언스테이크",
- "claimWithdraw": "출금 요청"
- },
"erc20": {
"approve": "승인"
}
@@ -1028,12 +1012,10 @@
"tradingUnavailable": "아직 ShapeShift에서는 %{assetSymbol} 거래가 불가능합니다. 대신 Elastic Swap에서 %{assetSymbol} 거래가 가능합니다.",
"tradeOnElasticSwap": "ElasticSwap에서 %{assetSymbol} 거래하기",
"otherOpportunitiesTitle": {
- "FOX": "다른 수익 기회",
- "FOXy": "수익 기회"
+ "FOX": "다른 수익 기회"
},
"otherOpportunitiesDescription": {
- "FOX": "유동성 공급, 이자 농사, 대출 및 차입 등의 다양한 기회를 확인하세요. 아래의 시작하기 링크를 클릭하여 관심있는 외부 사이트를 방문하여 자세히 알아보시고, FOX를 사용해보세요.",
- "FOXy": "이자 농사와 비용 수익을 제공하는, 업계 첫 DAO 리베이스 토큰인 FOXy를 빠르고 쉽게 구매해보세요!"
+ "FOX": "유동성 공급, 이자 농사, 대출 및 차입 등의 다양한 기회를 확인하세요. 아래의 시작하기 링크를 클릭하여 관심있는 외부 사이트를 방문하여 자세히 알아보시고, FOX를 사용해보세요."
},
"liquidityPools": "유동성 풀",
"farming": "이자 농사",
diff --git a/src/assets/translations/pt/main.json b/src/assets/translations/pt/main.json
index 1216252ee2f..fc4562763c5 100644
--- a/src/assets/translations/pt/main.json
+++ b/src/assets/translations/pt/main.json
@@ -581,13 +581,9 @@
"emptyWithdraws": "As solicitações de retirada pendentes serão exibidas aqui.",
"availableDate": "disponível em %{date}"
},
- "foxyOverview": {
+ "foxFarmingOverview": {
"withdrawals": "Saques",
"emptyWithdraws": "Saques pendentes ou em processo de pagamento serão exibidos aqui.",
- "availableDate": "Disponível %{date}",
- "disabledTitle": "Depósitos de FOXy estão desabilitados"
- },
- "foxFarmingOverview": {
"header": "Deposite tokens LP de ETH-FOX em %{opportunity} para ganhar",
"body": "Aprove e faça stake de seus tokens de liquidez para ganhar recompensas FOX.",
"rewards": "As recompensas que você ganhar serão acumuladas automaticamente.",
@@ -857,12 +853,6 @@
"swap": "Negocie",
"swapRefund": "Reembolso Swap"
},
- "foxy": {
- "stake": "Stake",
- "unstake": "Remover Staking",
- "instantUnstake": "Stake instantâneo ",
- "claimWithdraw": "Reivindicar Retirada"
- },
"erc20": {
"approve": "Aprovar",
"approveSymbol": "Aprovar %{symbol}",
diff --git a/src/assets/translations/ru/main.json b/src/assets/translations/ru/main.json
index 5b4f37946f2..0ead9c68aba 100644
--- a/src/assets/translations/ru/main.json
+++ b/src/assets/translations/ru/main.json
@@ -581,13 +581,9 @@
"emptyWithdraws": "Здесь отображаются незавершенные запросы на снятие стейкинга.",
"availableDate": "доступно %{date}"
},
- "foxyOverview": {
+ "foxFarmingOverview": {
"withdrawals": "Вывод средств",
"emptyWithdraws": "Здесь отображаются ожидающие или активные выводы средств.",
- "availableDate": "доступен %{date}",
- "disabledTitle": "Депозиты FOXy не работают"
- },
- "foxFarmingOverview": {
"header": "Внесите токены ETH-FOX LP на %{opportunity}, чтобы заработать",
"body": "Подтвердите и стейкайте свои токены ликвидности, чтобы получить бонусные вознаграждения FOX.",
"rewards": "Вознаграждения, которые вы заработаете, будут начисляться автоматически.",
@@ -857,12 +853,6 @@
"swap": "Торговля",
"swapRefund": "Возврат обмена"
},
- "foxy": {
- "stake": "Стейк",
- "unstake": "Разстейкать",
- "instantUnstake": "Мгновенная отмена стейкинга",
- "claimWithdraw": "Подтвердить вывод"
- },
"erc20": {
"approve": "Подтвердить",
"approveSymbol": "Утвердить %{symbol}",
diff --git a/src/assets/translations/tr/main.json b/src/assets/translations/tr/main.json
index 1f86248af18..2da15f2d398 100644
--- a/src/assets/translations/tr/main.json
+++ b/src/assets/translations/tr/main.json
@@ -581,13 +581,9 @@
"emptyWithdraws": "Bekleyen staking istekleri burada gösterilir.",
"availableDate": "kullanılabilir %{date}"
},
- "foxyOverview": {
+ "foxFarmingOverview": {
"withdrawals": "Para Çekme",
"emptyWithdraws": "Bekleyen veya etkin para çekme işlemleri burada gösterilir.",
- "availableDate": "kullanılabilir %{date}",
- "disabledTitle": "FOXy para yatırma işlemleri devre dışı bırakıldı"
- },
- "foxFarmingOverview": {
"header": "Kazanmak için ETH-FOX LP jetonlarını %{opportunity}'e yatırın",
"body": "Bonus FOX ödülleri kazanmak için likidite jetonlarınızı onaylayın ve stake edin.",
"rewards": "Kazandığınız ödüller otomatik olarak tahakkuk edecektir.",
@@ -857,12 +853,6 @@
"swap": "Takas",
"swapRefund": "Takas İadesi"
},
- "foxy": {
- "stake": "Stake Et",
- "unstake": "Stake Kaldır",
- "instantUnstake": "Stake'i Anında Geri Al",
- "claimWithdraw": "Ödülleri Topla"
- },
"erc20": {
"approve": "Onayla",
"approveSymbol": "%{symbol} Onayla",
diff --git a/src/assets/translations/uk/main.json b/src/assets/translations/uk/main.json
index b321bb806ba..1fe8227ace1 100644
--- a/src/assets/translations/uk/main.json
+++ b/src/assets/translations/uk/main.json
@@ -581,13 +581,9 @@
"emptyWithdraws": "Тут відображатимуться запити, що очікують на розблокування.",
"availableDate": "доступно %{date}"
},
- "foxyOverview": {
+ "foxFarmingOverview": {
"withdrawals": "Виведення",
"emptyWithdraws": "Тут відображаються очікувані або активні зняття коштів.",
- "availableDate": "доступно %{date}",
- "disabledTitle": "Депозити FOXy відключені"
- },
- "foxFarmingOverview": {
"header": "Внесіть LP токени ETH-FOX у %{opportunity}, щоб заробити",
"body": "Схвалюйте та стейкайте свої токени ліквідності, щоб отримати бонусні винагороди FOX.",
"rewards": "Зароблені вами винагороди будуть нараховуватися автоматично.",
@@ -857,12 +853,6 @@
"swap": "Торгівля",
"swapRefund": "Повернення коштів за обмін"
},
- "foxy": {
- "stake": "Стейк",
- "unstake": "Зняти зі стейкінгу",
- "instantUnstake": "Миттєве розблокування",
- "claimWithdraw": "Підтвердити зняття"
- },
"erc20": {
"approve": "Схвалити",
"approveSymbol": "Схвалити %{symbol}",
diff --git a/src/assets/translations/zh/main.json b/src/assets/translations/zh/main.json
index 2371d9dee79..50618da93cc 100644
--- a/src/assets/translations/zh/main.json
+++ b/src/assets/translations/zh/main.json
@@ -581,13 +581,9 @@
"emptyWithdraws": "此处将显示待处理的取消质押请求。",
"availableDate": "在 %{date} 可用"
},
- "foxyOverview": {
+ "foxFarmingOverview": {
"withdrawals": "提现",
"emptyWithdraws": "此处将显示待处理或进行的提现。",
- "availableDate": "在 %{date} 可用",
- "disabledTitle": "FOXy 充值已被禁用"
- },
- "foxFarmingOverview": {
"header": "充值 ETH-FOX LP 代币 %{opportunity} 来赚取收益",
"body": "授权并质押你的流动性代币以获得额外的 FOX 奖励。",
"rewards": "你获得的奖励将自动累积。",
@@ -857,12 +853,6 @@
"swap": "交易",
"swapRefund": "交换退款"
},
- "foxy": {
- "stake": "质押",
- "unstake": "取消质押",
- "instantUnstake": "立即取消质押",
- "claimWithdraw": "领取提款"
- },
"erc20": {
"approve": "授权",
"approveSymbol": "授权 %{symbol}",
diff --git a/src/components/StakingVaults/EarnOpportunities.tsx b/src/components/StakingVaults/EarnOpportunities.tsx
index 2c02767906b..068cfbfd3b9 100644
--- a/src/components/StakingVaults/EarnOpportunities.tsx
+++ b/src/components/StakingVaults/EarnOpportunities.tsx
@@ -1,6 +1,6 @@
import { Box, Card, CardBody, CardHeader, Heading, HStack } from '@chakra-ui/react'
import type { AccountId, AssetId } from '@shapeshiftoss/caip'
-import { foxAssetId, foxyAssetId, fromAssetId } from '@shapeshiftoss/caip'
+import { fromAssetId } from '@shapeshiftoss/caip'
import qs from 'qs'
import { useCallback, useEffect, useMemo } from 'react'
import { useLocation } from 'react-router-dom'
@@ -56,13 +56,13 @@ export const EarnOpportunitiesContent = ({ assetId, accountId }: EarnOpportuniti
() =>
!asset
? []
- : lpOpportunities.concat(stakingOpportunities).filter(
- row =>
- row.assetId.toLowerCase() === asset.assetId.toLowerCase() ||
- (row.underlyingAssetIds.length && row.underlyingAssetIds.includes(asset.assetId)) ||
- // show foxy opportunity in the foxy asset page
- (row.assetId === foxAssetId && asset.assetId === foxyAssetId),
- ),
+ : lpOpportunities
+ .concat(stakingOpportunities)
+ .filter(
+ row =>
+ row.assetId.toLowerCase() === asset.assetId.toLowerCase() ||
+ (row.underlyingAssetIds.length && row.underlyingAssetIds.includes(asset.assetId)),
+ ),
[asset, lpOpportunities, stakingOpportunities],
)
diff --git a/src/features/defi/contexts/DefiManagerProvider/utils.ts b/src/features/defi/contexts/DefiManagerProvider/utils.ts
index 4d66759c657..d8107bf7048 100644
--- a/src/features/defi/contexts/DefiManagerProvider/utils.ts
+++ b/src/features/defi/contexts/DefiManagerProvider/utils.ts
@@ -1,6 +1,5 @@
import { CosmosManager } from '@/features/defi/providers/cosmos/components/CosmosManager/CosmosManager'
import { FoxFarmingManager } from '@/features/defi/providers/fox-farming/components/FoxFarmingManager/FoxFarmingManager'
-import { FoxyManager } from '@/features/defi/providers/foxy/components/FoxyManager/FoxyManager'
import { ThorchainSaversManager } from '@/features/defi/providers/thorchain-savers/components/ThorchainSaversManager/ThorchainSaversManager'
import { DefiProvider, DefiType } from '@/state/slices/opportunitiesSlice/types'
@@ -14,7 +13,6 @@ export const DefiProviderToDefiModuleResolverByDeFiType = {
[DefiProvider.ThorchainSavers]: {
[`${DefiType.Staking}`]: ThorchainSaversManager,
},
- [DefiProvider.ShapeShift]: FoxyManager,
[DefiProvider.CosmosSdk]: CosmosManager,
}
// Not curried since we can either have a list of providers by DefiType, or a single one for providers not yet migrated to the abstraction
diff --git a/src/features/defi/providers/fox-farming/components/FoxFarmingManager/Overview/WithdrawCard.tsx b/src/features/defi/providers/fox-farming/components/FoxFarmingManager/Overview/WithdrawCard.tsx
index 78606d1aed5..645ee87769e 100644
--- a/src/features/defi/providers/fox-farming/components/FoxFarmingManager/Overview/WithdrawCard.tsx
+++ b/src/features/defi/providers/fox-farming/components/FoxFarmingManager/Overview/WithdrawCard.tsx
@@ -58,9 +58,9 @@ export const WithdrawCard = ({ asset, amount, expired }: WithdrawCardProps) => {
return (
-
+
{!hasClaim ? (
-
+
) : (