client.tradingWithdraws creates durable, signed withdrawal intents out of the Trading venue.
Every method is authenticated and account-scoped, so each input accepts an optional account field
("main", "active", or { subaccountId }). See Account scoping for how the default resolves.
Each intent is a signed message, not a fire-and-forget call. The SDK embeds a deadline (about five minutes), a random nonce, and an idempotency key into the payload, then signs it. Because the payload is signed, an intent is durable: submit it once, and it settles even if your process restarts.
Signing
Every withdrawal needs a signature over its canonical EIP-191 text message. You have two options, passed alongside the withdrawal input:
walletSigner: hand the SDK an EOA wallet signer and it builds the canonical message, asks the wallet to sign it withpersonal_sign, and submits the wallet-authorized intent.payloadSignature: supply a signature you precomputed yourself (for example from an API-key signer or custody infrastructure). The SDK submits the backend-authorized intent as-is.
Provide exactly one. If neither is present, the call throws a ValidationError.
import type { TradingWithdrawWalletSigner } from "@polyester/sdk";
const walletSigner: TradingWithdrawWalletSigner = {
signerWallet: owner.address,
accountId: me.accountId,
signMessage: (message) => owner.signMessage({ message }),
};Methods
| Method | Summary |
|---|---|
validateDestination | Check a destination chain and address before you sign anything. |
createToFunding | Withdraw from Trading to Funding, staying on Polyester. |
createToExternalChain | Withdraw from Trading out to an external chain via the Zipper. |
prepareToFunding | Sign a Trading-to-Funding intent once, submit it when you are ready. |
prepareToExternalChain | Sign an external-chain intent once, submit it when you are ready. |
validateDestination(input, options?)
Preflights a destination before you build or sign an intent. It takes a destinationChainId and a destinationAddress, and answers whether that pair is a legitimate withdrawal target.
const check = await client.tradingWithdraws.validateDestination({
destinationChainId: 1,
destinationAddress: "0xabc...",
});
if (!check.valid) {
console.error(check.code, check.message);
return;
}
// Use the canonical form the backend resolved, not the string you typed.
const destinationAddress = check.canonicalDestinationAddress;The result is a discriminated union on valid:
type ValidateWithdrawDestinationResult =
| {
valid: true;
code: "valid";
message: string;
canonicalDestinationAddress: string;
}
| {
valid: false;
code:
| "invalid_address"
| "unsupported_chain"
| "polyester_smart_account"
| "token_contract"
| "denylisted_address"
| "unspecified";
message: string;
canonicalDestinationAddress: string;
};| Code | Meaning |
|---|---|
valid | The address is a legitimate destination on that chain. |
invalid_address | The address is malformed for that chain. |
unsupported_chain | Withdrawals to that chain are not supported. |
polyester_smart_account | The address is a Polyester smart account; use an internal transfer. |
token_contract | The address is a token contract, so funds sent there would be stranded. |
denylisted_address | The address is blocked for that chain family. |
unspecified | The backend returned a code this SDK version does not have a label for. |
createToExternalChain rather than treating a valid result as an approval.createToFunding(input, options?)
Moves funds from the Trading venue to Funding. The destination stays on Polyester, so nothing leaves
the platform. Resolves to a CreateTradingWithdrawResult carrying the intentId.
const result = await client.tradingWithdraws.createToFunding({
assetId: 2,
quantity: "500.00",
idempotencyKey: crypto.randomUUID(),
walletSigner,
});
console.log(result.intentId);CreateTradingWithdrawToFundingInput
| Field | Type | Required | Notes |
|---|---|---|---|
assetId | number | yes | Integer from 1 through 4,294,967,295. |
quantity | decimal string | yes | Amount to withdraw. Strict precision. |
idempotencyKey | string | yes | Reuse it to retry safely. |
destinationAddress | string | no | Optional Funding destination address. |
walletSigner | TradingWithdrawWalletSigner | one signer | SDK signs the EIP-191 message. |
payloadSignature | Uint8Array | one signer | A signature you precomputed. |
account | AccountScope | no | Scope override. |
createToExternalChain(input, options?)
Withdraws from the Trading venue out to an external chain. The funds leave Polyester via the Zipper. The amount is gross (fees are taken from it), and you specify the destination network and address.
const result = await client.tradingWithdraws.createToExternalChain({
assetId: 2,
quantity: "500.00", // gross amount
destinationChainId: 1,
destinationAddress: "0xabc...",
idempotencyKey: crypto.randomUUID(),
walletSigner,
});
console.log(result.intentId);CreateTradingWithdrawToExternalChainInput
| Field | Type | Required | Notes |
|---|---|---|---|
assetId | number | yes | Integer from 1 through 4,294,967,295. |
quantity | decimal string | yes | Gross amount to withdraw. Strict precision. |
destinationChainId | number | yes | Destination network chain id. |
destinationAddress | string | yes | Destination address on that network. |
idempotencyKey | string | yes | Reuse it to retry safely. |
walletSigner | TradingWithdrawWalletSigner | one signer | SDK signs the EIP-191 message. |
payloadSignature | Uint8Array | one signer | A signature you precomputed. |
account | AccountScope | no | Scope override. |
prepareToFunding(input) and prepareToExternalChain(input)
Both create methods are a prepare-then-submit pair collapsed into one call. Use the prepare variants when submission can be interrupted, most often by an MFA step-up: they build and sign the
payload once and hand back a PreparedTradingWithdraw whose submit resends that exact payload and
signature. Only transport options change between attempts, so the wallet is never asked to sign
twice for the same withdrawal.
const prepared = await client.tradingWithdraws.prepareToFunding({
assetId: 2,
quantity: "500.00",
idempotencyKey: crypto.randomUUID(),
walletSigner,
});
try {
await prepared.submit();
} catch (error) {
// Satisfy the step-up, then resubmit the same signed payload.
await prepared.submit({ stepUpToken });
}They take the same inputs as their create counterparts.
import type { CreateTradingWithdrawResult, PolyesterMutationOptions } from "@polyester/sdk";
interface PreparedTradingWithdraw {
submit: (options?: PolyesterMutationOptions) => Promise<CreateTradingWithdrawResult>;
}CreateTradingWithdrawResult
Every submission path returns the same shape:
interface CreateTradingWithdrawResult {
intentId: string;
}Track the intent from there through the lifecycle stream. A trading withdrawal that fails after
admission surfaces its cause as a lifecycle reason: trading_withdraw_policy_denied, trading_withdraw_contract_reverted, or trading_withdraw_execution_failed.
Related
- Zipper for the cross-chain bridge that carries an external-chain withdrawal.
- Lifecycle to follow an intent from signed to settled.
- Guard signer for co-signing.
- Deposits and withdrawals guide for the end-to-end walkthrough.
- Requests and idempotency for retry semantics and idempotency keys.