Skip to main content

Portal Deposit Addresses

Give a user a reusable, public, exchange-style deposit address. Anyone can fund it with standard ERC-20 transfers, and the funds are credited into the owner’s shielded balance without revealing which account was credited. See Portal Deposit Addresses for the concept and trust model.
Preview feature — pending external audit, and enabled per deployment. Several fields are launch-limited; the limitations are called out inline below.
The TypeScript SDK exposes the full portal lifecycle through the sdk.portal resource.

Checking support

delegateAddress() returns the portal delegate address advertised by the server’s /info, or undefined when the server does not support portals:
if (!sdk.portal.delegateAddress()) {
  throw new Error('This server does not support portal deposits.');
}

Creating a portal

create does everything in one call: it derives a fresh portal address E from your seed, delegates it to the portal implementation, registers the on-chain owner binding, and publishes the discovery registry entry.
const portal = await sdk.portal.create();
console.log('Deposit address:', portal.address); // share this E publicly
console.log('Derivation index:', portal.index);
Pin a derivation index to re-derive a specific portal deterministically:
const portal = await sdk.portal.create({ index: 0 });

Listing your portals

const portals = await sdk.portal.list();
for (const p of portals) {
  console.log(p.address, p.status.registered, p.status.published);
}

Checking a portal’s status

const status = await sdk.portal.status(portal.address);
// { registered: boolean, delegated: boolean, published: boolean }
Launch limitation: status.delegated always reports false — the discovery server does not yet compute on-chain delegation status. A portal you created with create() is delegated regardless. Do not gate a “ready” check on this field; use registered and published.

Receiving deposits

A sender funds a portal with an ordinary ERC-20 transfer to E — they need nothing from Privacy Boost. List the deposits observed at a portal:
const deposits = await sdk.portal.deposits(portal.address);
for (const d of deposits) {
  console.log(d.portalDepositId, d.state, d.netAmount);
}
A deposit moves through these states:
stateMeaning
pending_sweepFunds are at E, not yet swept into the pool
creditedSwept and credited to your shielded balance
cancellableEligible to be reclaimed
cancelledReclaimed back to E
unknownState could not be determined
Launch limitations: cancellableAtBlock is always 0 (treat as unknown, not “reclaimable now”), and at launch the sweep fee is 0, so grossAmount equals netAmount.

Sweeping

Sweeping moves a portal’s balance into the pool. At launch this is operator-run, so you normally do not call it — but sweep is available as a self-service backstop. It returns the sweep transaction hash:
const txHash = await sdk.portal.sweep(portal.address, tokenId);

Reclaiming an un-credited deposit

If a swept deposit is never credited, reclaim it after the cancel delay. The funds return to the portal address E, never to the caller:
const txHash = await sdk.portal.reclaim(depositId);

Withdrawing raw funds from E (escape hatch)

For funds resting at E that can never be swept (for example a token that is not registered with the protocol), withdraw builds a signed raw EIP-1559 transaction you broadcast yourself. Read the nonce and gas/fee values from your own RPC:
const signedRawTx = sdk.portal.withdraw({
  index: portal.index,
  token: '0x...token-address',
  to: '0xDestination',
  amount: 1000000000000000000n,
  nonce: await publicClient.getTransactionCount({ address: portal.address }),
  gasLimit: 100000n,
  maxFeePerGas: 20000000000n,
  maxPriorityFeePerGas: 1000000000n,
});
// Broadcast signedRawTx yourself, e.g. publicClient.sendRawTransaction({ serializedTransaction: signedRawTx })

Types

interface Portal {
  address: string; // the portal deposit address E
  index: number;   // seed-derivation index — re-derives E's key
  bindH: string;   // on-chain owner binding H (0x-prefixed 32-byte hex)
}

type PortalDepositState =
  | 'pending_sweep'
  | 'credited'
  | 'cancellable'
  | 'cancelled'
  | 'unknown';

interface PortalStatus {
  registered: boolean;
  delegated: boolean; // launch limitation: always false (see above)
  published: boolean;
}

interface PortalInfo {
  address: string;
  index?: number;
  status: PortalStatus;
}

interface PortalDeposit {
  portalDepositId: string; // key for reclaim()
  portal: string;
  tokenId: number;
  grossAmount: bigint;
  netAmount: bigint;
  state: PortalDepositState;
  requestBlock: number;
  cancellableAtBlock: number; // launch limitation: always 0 (treat as unknown)
}

interface CreatePortalOptions {
  index?: number;
}

interface WithdrawPortalParams {
  index: number;
  token: string;
  to: string;
  amount: bigint;
  nonce: number | bigint;
  gasLimit: bigint;
  maxFeePerGas: bigint;
  maxPriorityFeePerGas: bigint;
}