Skip to main content
Read endpoints for withdrawals use deposits.read scope. Create still requires withdrawals.write.

Overview

  • GET /partner/v1/withdrawals/assets: list destination assets.
  • POST /partner/v1/withdrawals/range: get min/max for a destination pair.
  • POST /partner/v1/withdrawals/estimate: quote destination receive amount for a given amount.
  • POST /partner/v1/withdrawals: create relay + fetch Rain withdrawal signature payload.
POST /partner/v1/withdrawals uses a breaking request body shape with source and destination objects.
Withdrawal routes are dynamic (BTC/SOL/EVM and others) and can change. Always call assets, range, and estimate before create. In sandbox, source is fixed to rUSD on Base Sepolia. source.contractId is optional.

Flow

API flows (step-by-step)

1

1) Discover destination assets

Endpoint: GET /partner/v1/withdrawals/assets
curl -s \
  -H "Authorization: Bearer $PARTNER_SESSION_TOKEN" \
  "https://api.machines.cash/partner/v1/withdrawals/assets?q=hbar&limit=20"
You can optionally pass sourceChainId and sourceTokenAddress as hints, but they are not required.
2

2) Get min/max for chosen route

Endpoint: POST /partner/v1/withdrawals/range
curl -s \
  -H "Authorization: Bearer $PARTNER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": { "currency": "hbar", "network": "hbar" }
  }' \
  https://api.machines.cash/partner/v1/withdrawals/range
3

3) Estimate destination receive amount

Endpoint: POST /partner/v1/withdrawals/estimate
curl -s \
  -H "Authorization: Bearer $PARTNER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": { "currency": "hbar", "network": "hbar", "extraId": "optional-tag" },
    "amountCents": 2500
  }' \
  https://api.machines.cash/partner/v1/withdrawals/estimate
4

4) Create withdrawal signature payload (breaking schema)

Endpoint: POST /partner/v1/withdrawals
curl -s \
  -H "Authorization: Bearer $PARTNER_SESSION_TOKEN" \
  -H "Idempotency-Key: wdrl-123456" \
  -H "Content-Type: application/json" \
  -d '{
    "amountCents": 2500,
    "source": {
      "contractId": "optional-uuid"
    },
    "destination": {
      "currency": "hbar",
      "network": "hbar",
      "address": "0.0.123456",
      "extraId": "optional-tag"
    },
    "adminAddress": "0x..."
  }' \
  https://api.machines.cash/partner/v1/withdrawals
Ready responses include:
  • execution info for contract call routing.
  • relay metadata (payinAddress, payinExtraId, payoutAddress, payoutExtraId, route ids).
5

5) Execute onchain

  • from must be the same admin EOA used in the create request (adminAddress).
  • to must be execution.callTarget (controller or coordinator), not the collateral proxy address.
  • Use parameters in order: [collateralProxy, token, amount, recipient, expiresAt, executorSalt, executorSignature].
  • If execution.callPath = controller_v1, call 7-arg withdrawAsset(…).
  • If execution.callPath = coordinator_v2, call 10-arg withdrawAsset(…) and include v2 admin signature values.
  • If status is pending, wait retryAfterSeconds and retry the same request with the same idempotency key.

Onchain execution details

For coordinator_v2, you must generate an additional admin signature before calling the contract.
// response from POST /partner/v1/withdrawals
const { data } = withdrawalResponse;
if (data.status !== "ready" || !data.parameters || !data.execution?.callTarget) {
  throw new Error("withdrawal signature is not ready");
}

const [collateralProxy, token, amount, recipient, expiresAt, executorSalt, executorSignature] =
  data.parameters;

// tx sender must match adminAddress used in create request
const adminAddress = "0x..."; // same value you sent as adminAddress

if (data.execution.callPath === "controller_v1") {
  // to = data.execution.callTarget
  await controller.withdrawAsset(
    collateralProxy,
    token,
    amount,
    recipient,
    expiresAt,
    executorSalt,
    executorSignature,
  );
} else if (data.execution.callPath === "coordinator_v2") {
  // 1) Read nonce from collateral proxy
  const nonce = await collateral.adminNonce();

  // 2) Build typed data
  const adminSalt = randomBytes(32);
  const domain = {
    name: "Collateral",
    version: "2",
    chainId: data.execution.chainId,
    verifyingContract: collateralProxy,
    salt: hexlify(adminSalt),
  };
  const types = {
    Withdraw: [
      { name: "user", type: "address" },
      { name: "asset", type: "address" },
      { name: "amount", type: "uint256" },
      { name: "recipient", type: "address" },
      { name: "nonce", type: "uint256" },
    ],
  };
  const message = {
    user: adminAddress,
    asset: token,
    amount: BigInt(amount),
    recipient,
    nonce,
  };
  const adminSignature = await signer.signTypedData(domain, types, message);

  // 3) Call coordinator (to = data.execution.callTarget)
  await coordinator.withdrawAsset(
    collateralProxy,
    token,
    amount,
    recipient,
    expiresAt,
    executorSalt,
    executorSignature,
    [adminSalt],
    [adminSignature],
    true,
  );
}

Relay metadata fields

relay.changeNowId
string
Provider exchange id for relay tracking.
relay.payinAddress
string
Address Rain withdrawal sends funds to.
relay.payinExtraId
string
Optional memo/tag required on relay pay-in side.
relay.payoutAddress
string
Final destination address configured on the relay.
relay.payoutExtraId
string
Optional destination memo/tag configured for payout.