Skip to main content
The PartnerAccountService creates sub-account profiles linked to the authenticated partner. Requires HMAC authentication with the account_creation scope.

Access

import { Client } from '@limitless-exchange/sdk';

const client = new Client({
  baseURL: 'https://api.limitless.exchange',
  hmacCredentials: { tokenId, secret },
});

// Use client.partnerAccounts.*

Server wallet mode

Creates a managed Privy wallet for the sub-account. Enables delegated signing — the partner submits unsigned orders and the server signs them.
const account = await client.partnerAccounts.createAccount({
  displayName: 'user-alice',
  createServerWallet: true,
});

console.log(account.profileId); // numeric profile ID
console.log(account.account);   // wallet address
New server wallets should be checked with checkAllowances() before the first delegated trade. If retryable targets are missing or failed, call retryAllowances() and poll again.

List and recover sub-accounts

Use listAccounts() to list partner-owned sub-accounts or recover a specific child profile by wallet address. This calls GET /profiles/partner-accounts and requires HMAC credentials with the account_creation scope.
const accounts = await client.partnerAccounts.listAccounts({
  limit: 25,
  page: 1,
});

for (const account of accounts.data) {
  console.log(account.profileId, account.account, account.displayName);
}

const recovered = await client.partnerAccounts.listAccounts({
  account: '0x1676716Ef7F19B5C5d690631CB57cf0bFD900A3d',
  limit: 25,
  page: 1,
});

const profileId = recovered.data[0]?.profileId;
ParameterDescription
accountOptional exact EVM address filter for recovering a known child account
limitOptional page size. The API caps values above 25 to 25
pageOptional 1-indexed page number
Do not send x-on-behalf-of to this endpoint. Results are always scoped to the authenticated partner behind the HMAC token.

Allowance recovery

Server-wallet sub-accounts need delegated-trading approvals before they can trade. The partner allowance helpers use the Partner API only:
  • checkAllowances(profileId) calls GET /profiles/partner-accounts/:profileId/allowances
  • retryAllowances(profileId) calls POST /profiles/partner-accounts/:profileId/allowances/retry
  • both methods require HMAC credentials with account_creation and delegated_signing
  • profileId is the child/server-wallet profile id
import { APIError, RateLimitError } from '@limitless-exchange/sdk';

let allowances = await client.partnerAccounts.checkAllowances(account.profileId);

if (!allowances.ready) {
  const retryableTargets = allowances.targets.filter(
    (target) =>
      target.retryable &&
      (target.status === 'missing' || target.status === 'failed'),
  );

  if (retryableTargets.length > 0) {
    try {
      allowances = await client.partnerAccounts.retryAllowances(account.profileId);
    } catch (error) {
      if (error instanceof RateLimitError) {
        console.log(error.data?.retryAfterSeconds);
      } else if (error instanceof APIError && error.status === 409) {
        console.log('Retry already running; poll checkAllowances again shortly.');
      } else {
        throw error;
      }
    }
  }
}

if (allowances.targets.some((target) => target.status === 'submitted')) {
  // A sponsored tx/user operation was submitted by this retry request.
  // Poll checkAllowances() again after a short delay to observe confirmed chain state.
}
Recommended partner flow:
  1. Poll checkAllowances(profileId).
  2. If ready === true, continue.
  3. If targets are missing or failed with retryable === true, call retryAllowances(profileId).
  4. If retry returns submitted targets, poll checkAllowances() again after a short delay.
  5. If retry returns 429, wait retryAfterSeconds.
  6. If retry returns 409, wait briefly and call checkAllowances() again.

Withdrawal address allowlist

Explicit treasury destinations for server-wallet withdrawals must be allowlisted on the authenticated partner profile unless the destination is already the partner account or partner smart wallet. Allowlist management uses a Privy identity token, not API-token/HMAC auth.
const identityToken = process.env.PRIVY_IDENTITY_TOKEN!;
const treasuryAddress = '0x0F3262730c909408042F9Da345a916dc0e1F9787';

const entry = await client.partnerAccounts.addWithdrawalAddress(identityToken, {
  address: treasuryAddress,
  label: 'treasury',
});

console.log(entry.destinationAddress);

await client.partnerAccounts.deleteWithdrawalAddress(identityToken, treasuryAddress);
addWithdrawalAddress() and deleteWithdrawalAddress() call POST /portfolio/withdrawal-addresses and DELETE /portfolio/withdrawal-addresses/:address with the identity: Bearer <token> header. POST /portfolio/withdraw still uses HMAC auth with the withdrawal scope.

EOA mode

Creates a profile for an externally-owned address. The end user manages their own keys and signs orders themselves. EOA mode requires wallet ownership verification headers:
const account = await client.partnerAccounts.createAccount(
  {
    displayName: 'user-bob',
  },
  {
    account: '0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed',
    signingMessage: '0x...',
    signature: '0x...',
  },
);
HeaderDescription
accountChecksummed Ethereum address (EIP-55)
signingMessageHex-encoded signing message
signatureHex-encoded signature from the wallet

Validation

  • displayName is optional, max 44 characters. Defaults to the wallet address if omitted.
  • Returns 409 Conflict if a profile already exists for the target address.
  • Cannot create a sub-account for the partner’s own address.
  • The SDK validates displayName length locally before sending the request.