Polyester applies different security controls according to the movement flow and caller type. An interactive Trading withdrawal, an API-key withdrawal, a Trading-to-Trading internal transfer, and an on-chain Funding transfer do not share one authentication model.
Security requirements by flow
- Trading withdrawal · Interactive JWT
JWT, wallet-signed intent, recent MFA when the Main Account owner moves Trading to Funding or withdraws to an explicitly listed external destination; otherwise fresh step-up, plus source and movement authorization. - Trading withdrawal · Ed25519 API key
Signed HTTP request, signed withdrawal payload, mandatory API-key policy. - Trading-to-Trading internal transfer · Interactive JWT
JWT, recent MFA elevation when the Main Account owner transfers within the same ownership tree or to an explicitly listed destination; otherwise fresh step-up. - Trading-to-Trading internal transfer · Ed25519 API key
Signed HTTP request and mandatory API-key policy. - Funding-to-Funding on-chain transfer · Smart account
Wallet-authorized on-chain transaction and applicable contract whitelist checks. - Protected Funding whitelist change · Interactive owner
Owner JWT, fresh step-up, wallet-authorized transaction, and GuardSigner approval.
Destination whitelist checks are additional to the controls shown above when the selected source account requires them.
Interactive and API-key callers
Interactive callers
Interactive movement methods authenticate a normal JWT session. For a newly accepted request:
CreateWalletTradingWithdraw(RESTPOST /v1/chain/trading-withdraws:wallet) accepts a recent MFA-elevated session when the Main Account owner moves funds from Trading to Funding inside one ownership tree or when an external destination is explicitly listed in the chain-confirmed whitelist. Other cross-owner, delegated, and external movements require fresh step-up. Every request includes an EIP-191personal_signwithdrawal intent signed by an active wallet bound to the selected account.CreateInternalTransfer(RESTPOST /v1/trading/internal-transfers) accepts a recent MFA-elevated session when the Main Account owner moves funds inside the same ownership tree or when the destination is explicitly listed in the chain-confirmed internal whitelist. Other cross-owner and delegated transfers require a fresh step-up. It does not use a separate wallet-signed movement intent.
An address-book entry or a destination that is merely allowed because its whitelist requirement is disabled does not receive the listed-destination MFA relaxation.
API-key callers
API-key movement does not use interactive MFA:
CreateTradingWithdraw(RESTPOST /v1/chain/trading-withdraws) requires normal Ed25519 HTTP authentication and a separate Ed25519 signature over the deterministic withdrawal payload.CreateInternalTransferuses the signed HTTP request body and does not have a separate movement signature field.
An API-key policy must explicitly allow the requested movement. The key remains bound to its Main
Account or configured Subaccount and cannot use the request to escape that scope. A key whose
policy lacks the action is refused with PERMISSION_DENIED, and the message names the missing
action, for example api key policy does not allow EXTERNAL_WITHDRAW.
See Ed25519 API keys for HTTP signing and API key replay protection for retry guidance.
Account selection, roles, and policies
The authenticated identity determines the Main Account. A JWT caller can optionally select an accessible Subaccount. Moving funds from a Subaccount requires the owner, administrator, or treasury role.
For API keys:
- a Main Account key moves funds only from the Main Account
- a Subaccount-scoped key moves funds only from its bound Subaccount
- the API-key policy must allow the movement action
- an attached Subaccount policy must allow the same movement action
For interactive Subaccount callers, an attached Subaccount policy must allow the movement action. Main Account movement does not use a generic Subaccount policy.
Wallet-signed Trading withdrawal intents
The interactive Trading withdrawal method requires an EIP-191 personal_sign message signed by a
wallet bound to the selected account. This signature is independent of the wallet's currently
selected Network. The signed facts include:
- movement type
- source and target account
- Asset and amount
- Polyester Chain, destination chain, and destination address
- deadline and nonce
- idempotency key
- chain and verifying-contract context
Polyester verifies the signature against the complete request. Changing a signed field invalidates the intent.
API-key Trading withdrawals use a different Ed25519 movement payload. Do not submit a wallet intent to the API-key method.
MFA for Trading movements
Interactive Main Account owners may reuse an MFA-elevated session completed within the previous 30 minutes for Trading-to-Funding withdrawals, same-owner Trading-to-Trading internal transfers, and movements to explicitly listed external or internal destinations. Other cross-owner, delegated, and external interactive movements require fresh step-up.
For fresh-step-up movement, Polyester consumes the single-use proof before locking funds, creating a ledger reserve or transfer, publishing movement lifecycle state, or queueing execution. If consumption cannot be confirmed, movement does not begin. Retrying the same accepted request reuses its recorded authorization and does not consume another proof; changing the movement facts conflicts with the original idempotent request.
API-key movement does not prompt for MFA. Its security boundary is the API-key signature, strict account scope, mandatory API-key policy, destination enforcement, and movement invariants.
See Multi-factor authentication for interactive step-up behavior.
External and internal whitelists
Polyester has two movement whitelists. The external whitelist lists a Network plus a destination address and applies to withdrawals to other Networks. The internal whitelist lists Polyester smart-account addresses and applies to internal account movement. Each source account has a separate requirement flag for each: when a flag is enabled, the exact destination must be listed.
Whitelist state is chain-authoritative. Adding or removing destinations, and disabling a requirement, are on-chain actions that need a GuardSigner approval, which accepts interactive owner sessions with a fresh step-up only. Saving an address-book entry does not whitelist it. Trading APIs enforce a mirror of the chain-confirmed state and fail closed when the mirror is not loaded. The full model, the read methods, and the refusal codes are in Whitelists; approvals are in GuardSigner.
Movement-specific behavior
Trading withdrawals
A Trading withdrawal can:
- move Trading funds to the selected account's own Funding Account
- move Trading funds to another Funding smart-account address
- withdraw Trading funds to an external Network and address
The own-Funding path omits a destination override. Another Funding address is subject to the internal whitelist requirement. An external destination is subject to the external whitelist requirement for the selected Network.
Interactive callers always provide the wallet-signed intent. Trading-to-Funding moves and withdrawals to explicitly listed external destinations by the Main Account owner accept recent MFA; other cross-owner, delegated, and external movements require fresh step-up. API-key callers always provide the API-key withdrawal signature and never use interactive MFA.
Trading-to-Trading internal transfers
An internal transfer targets the Unified Trading Account of another Main Account or Subaccount by its supported public identifier or smart-account address. Self-transfer to the identical source account is rejected.
An interactive Main Account owner may reuse MFA completed within the previous 30 minutes for transfers inside the same ownership tree or to an explicitly listed destination. Other cross-owner and delegated interactive callers require fresh step-up. API-key callers require a policy that allows internal transfers. No caller receives a same-owner exemption from an enabled internal whitelist.
Funding-to-Funding transfers
Funding movement is an on-chain account action, not the Trading internal-transfer API. The selected smart account submits wallet-authorized calldata, and the Funding contract enforces its internal whitelist requirement.
Safe retries
Trading withdrawal and Trading internal-transfer requests require an idempotency key. Generate it before the first attempt and persist it with the movement intent.
For a retry:
- reuse the idempotency key
- keep all movement facts unchanged
- preserve the original wallet or API-key movement signature when the request contains one
- create a fresh HTTP timestamp and signature for each API-key request attempt
Retry a signed Trading withdrawal only while its original deadlineTsSec remains valid. The API
validates the deadline before looking up the idempotent result, so an expired retry can be rejected
even when the first attempt succeeded.
Changing the source, destination, Asset, amount, or signed movement payload while reusing the key can produce a conflict instead of a second movement.