OneSwap WalletDocumentation

Product and technical spec

OneSwap Wallet

OneSwap Wallet is a hosted Canton wallet product for external-party wallets. It combines Google-backed encrypted recovery with client-side private-key control, Canton provisioning, token transfers, validator fee reimbursement, consolidation, and Featured App marker automation.

Short version: the UX is Privy-like because users can sign in with Google and restore a wallet without manually handling keys, but the custody model stays local because the browser encrypts and signs with the private key.

System design

Architecture

The frontend is responsible for wallet key generation/import, encrypted vault storage, Google Drive backup, and local signing. The backend is responsible for wallet-scoped auth, Canton/validator API calls, transaction preparation, transaction submission, balances, activity, and automation.

1Browser app

Encrypts vault, stores local state, signs challenges and tx hashes.

2Wallet backend

Prepares Canton operations and submits signed payloads.

3Canton services

Participant JSON API, validator API, transfer factory, and marker contracts.

Trust model

Custody and security

  • The backend never stores or receives raw private keys.
  • The browser stores an encrypted private-key vault in IndexedDB.
  • Google Drive stores only the encrypted vault JSON in the hidden appDataFolder.
  • Wallet actions require a backend challenge signed locally by the wallet key.
  • API access is layered: project API keys identify the integration, wallet sessions come from signed challenges, and write paths can require Turnstile verification before preparation or submission.
  • Turnstile and rate limits protect provisioning, writes, reads, and transfers.

Capabilities

Features

Create/importGenerate a browser keypair or import a PKCS#8 hex private key.
Google restoreRecover encrypted vaults from Drive app data.
BalancesShow spendable balances, locked holdings, USD values, and QR/copy address.
Send assetsPrepare, review, sign, and submit CC or token-standard sends.
Incoming offersAccept or reject incoming token transfers after local signing.
HistoryTrack sends, accepts, rejects, update ids, tx hashes, and counterparties.
ConsolidationPrepare MergeDelegation setup and holdings merge transactions.
MarkersRecord eligible non-CC activity and submit Featured App marker batches.

Lifecycle

Core flows

Create wallet

  1. User enters a 6-digit wallet PIN and signs in with Google if backup is required.
  2. Browser checks for an existing Google Drive backup before creating a new wallet.
  3. Browser generates a keypair and signs a backend challenge locally.
  4. Backend prepares topology and activation transactions.
  5. Browser signs hashes locally; backend submits and activates the wallet.
  6. Browser saves the encrypted local vault and uploads encrypted backup.

Send asset

  1. Backend prepares the main transfer and returns a tx hash.
  2. When reimbursement is enabled, backend estimates traffic and prepares a CC fee transfer.
  3. Frontend shows a confirmation modal with amount, recipient, and fee.
  4. Browser signs prepared hashes locally.
  5. Backend submits the main transfer and then the fee transfer.

Recovery policy

Google backup and one-wallet UX

The desired account policy is one wallet per Google account. On create/import, the app should check Drive appDataFolder first. If an encrypted backup already exists, it should direct the user to restore instead of creating another wallet.

Google sign-in is used for recovery and backup discovery. It is not server custody and it does not let OneSwap decrypt the wallet.

Holdings

Consolidation

Consolidation prepares MergeDelegation setup when required, then prepares merge transactions for assets with multiple unlocked holdings. This keeps wallet balances usable when one asset is split across many small holdings.

Featured App

Marker automation

Completed non-CC wallet activity is recorded and submitted later throughBatchedMarkersProxy_CreateMarkersV2. The service queries FeaturedAppRight and BatchedMarkersProxy, waits for enough batch weight, then submits the marker batch.

Marker submission is backend automation. It does not change wallet custody or signing: user transfers are still signed locally, while eligible wallet activity is batched later for Featured App accounting.

Backend

API surface

All public wallet routes are versioned under /api/v1.

Wallet auth

POST/api/v1/auth/challenge
POST/api/v1/auth/verify

Provisioning

POST/api/v1/wallets/provision/prepare
POST/api/v1/wallets/provision/submit
GET/api/v1/wallets/:partyId/preapproval
POST/api/v1/wallets/:partyId/activate

Operations

GET/api/v1/wallets/:partyId/balances
GET/api/v1/wallets/:partyId/incoming-transfers
POST/api/v1/wallets/:partyId/transfers/prepare
POST/api/v1/wallets/:partyId/transfers/submit
POST/api/v1/wallets/:partyId/consolidation/prepare
POST/api/v1/wallets/:partyId/consolidation/delegation/prepare

Economics

Network fee reimbursement

When enabled, sends prepare an additional CC reimbursement transfer to the treasury party. The transfer memo/reason is transfer_fee. The charged amount is the main prepared transfer's estimated traffic cost plus a configurable buffer.

The fee is shown to the user before signing. The wallet signs the primary asset transfer and the separate reimbursement transfer locally, so the user can review the full cost before submit.

Partners

Integration notes

OneSwap Wallet is a hosted web wallet with backend APIs and local browser signing. External apps can open the wallet popup provider at /provider and interact over postMessage. The current partner surface is intentionally narrow: it exposes the active Canton account and exact UTF-8 challenge signing with Ed25519.

Temporary bridge

OneSwap DEX auth challenge

This temporary integration is intentionally limited to wallet identity and auth-challenge signing for OneSwap DEX. It does not expose generic ledger APIs, prepared transaction signing, or broad wallet provider semantics.

The current bridge is designed for one narrow purpose: let OneSwap DEX discover the active Canton wallet in the browser and request an Ed25519 signature over the exact OneSwap auth challenge text.
Active accountThe wallet exposes the active Canton partyId and the wallet publicKey.
Signing algorithmThe current implementation uses Ed25519.
Message encodingThe wallet signs the exact OneSwap challenge message as UTF-8 text.
Message handlingThe wallet does not rewrite the message and does not add any wallet prefix before signing.

How the DEX flow works

1Open popup

The DEX opens /provider in a popup controlled by the wallet origin.

2Request account

The DEX asks the popup for the active Canton account so it can identify the user wallet.

3Request signature

The DEX sends the exact OneSwap challenge text and the wallet asks the user to approve signing.

4Verify on DEX

The DEX receives the Ed25519 signature, public key, and partyId and verifies them on its side.

Temporary popup surface

Provider routeThe DEX opens /provider in a popup and communicates over postMessage.
Account methodcanton_listAccounts returns the currently active Canton account.
Sign methodcanton_signMessage signs the exact OneSwap auth challenge after user approval.
User approvalThe popup requires explicit approval and prompts for the wallet PIN if the local vault is locked.

Example popup lifecycle

A typical auth flow from the DEX side looks like this:

// 1. Open the wallet popup
const popup = window.open('https://wallet.oneswap.cc/provider', 'oneswap_wallet_provider');

// 2. Wait for the wallet popup to announce readiness
window.addEventListener('message', (event) => {
  if (event.data?.source === 'oneswap_wallet_provider' && event.data?.type === 'ready') {
    // 3. Ask for the active account
    popup?.postMessage({
      source: 'oneswap_wallet_client',
      version: 1,
      id: 'req_accounts_1',
      method: 'canton_listAccounts'
    }, 'https://wallet.oneswap.cc');
  }
});

After receiving the active account, the DEX can request a challenge signature:

popup?.postMessage({
  source: 'oneswap_wallet_client',
  version: 1,
  id: 'req_sign_1',
  method: 'canton_signMessage',
  params: {
    partyId: '<active canton party id>',
    message: 'Sign in to OneSwap DEX\nChallenge: 7f6c...\nNonce: 12345',
    encoding: 'utf8'
  }
}, 'https://wallet.oneswap.cc');

Example responses

Account response:

{
  "source": "oneswap_wallet_provider",
  "version": 1,
  "id": "req_accounts_1",
  "method": "canton_listAccounts",
  "success": true,
  "result": [
    {
      "partyId": "<active canton party id>",
      "publicKey": "<wallet public key hex>",
      "signingAlgorithm": "ed25519"
    }
  ]
}

Challenge-signing response:

{
  "source": "oneswap_wallet_provider",
  "version": 1,
  "id": "req_sign_1",
  "method": "canton_signMessage",
  "success": true,
  "result": {
  "partyId": "<active canton party id>",
  "publicKey": "<wallet public key hex>",
  "signingAlgorithm": "ed25519",
  "signature": "<ed25519 signature hex>"
  }
}

Error handling

wallet_not_readyNo provisioned OneSwap Wallet exists in the browser yet.
request_in_progressThe popup is already handling another approval request.
user_rejectedThe user explicitly rejected the account-share or signing request.
Validation errorsThe wallet rejects missing challenge text, unsupported encodings, or a mismatched requested partyId.

This section documents only the current OneSwap DEX auth bridge. Full provider compatibility or broader transaction-signing methods can be added later without changing the wallet’s local-signing custody model.