open-standards/openpay/crates/op-payouts/src/wise.rs
Transaction Science 7ef14c2ea9 ingest wave B: 3 crates · op-payouts + smart-byte-nostr + eoc-streaming
OpenPay (1 crate, 2174 LOC, 14 tests)
  op-payouts          Multi-rail payout engine. Visa Direct, Mastercard Send,
                      ACH credits (NACHA), Fedwire + SWIFT MT103 + ISO 20022
                      pacs.008, SEPA SCT + SCT Inst (mod-97 IBAN), UK FPS,
                      FedNow, RTP, PayPal, Wise. Crypto: USDC/USDT on
                      ETH/Polygon/Base/Solana + BTC + Lightning with real
                      ERC-20 transfer(address,uint256) calldata. Offline-pure
                      drivers (emit wire payloads — operators transmit).

Smart Byte (1 crate, 2605 LOC, 38 tests)
  smart-byte-nostr    Nostr adapter. NIP-01 canonical event id + BIP-340
                      Schnorr sign/verify; from-scratch bech32 + NIP-19 TLV
                      (npub/nsec/note/nprofile/nevent/nrelay); NIP-04 legacy
                      AES-256-CBC DMs; NIP-44 v2 HKDF-SHA256→ChaCha20+HMAC
                      with bucketed padding; NIP-17 gift-wrap (kind 1059) +
                      seal (kind 13) + rumor (kind 14); NIP-05/11/13/26/65.
                      Relay client message vocabulary (REQ/EVENT/CLOSE/AUTH/
                      EOSE/OK/NOTICE/CLOSED) + in-memory router.

EOC (1 crate, 2024 LOC, 30 tests)
  eoc-streaming       Token-by-token + tool-call streaming primitives.
                      SSE framing (data: lines); Anthropic event mapper
                      (message_start/content_block_*/message_delta/stop/ping);
                      OpenAI delta mapper; Vercel AI SDK mapper. Bounded-channel
                      backpressure with watermarks; CancelToken propagation;
                      Last-Event-ID resumable streams; per-stream joule meter.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-24 13:39:09 -04:00

88 lines
2.8 KiB
Rust

//! Wise Platform API driver.
//!
//! Wise's payout flow is three calls:
//! 1. `POST /v3/profiles/{profile}/quotes` — quote the FX.
//! 2. `POST /v1/transfers` — create the transfer against a recipient
//! id.
//! 3. `POST /v3/profiles/{profile}/transfers/{id}/payments` — fund.
//!
//! The orchestrator only needs the transfer creation body offline; the
//! quote and funding calls are operator-driven.
use serde::Serialize;
use uuid::Uuid;
use crate::error::{Error, Result};
use crate::payout::{
BeneficiaryAccount, Payout, PayoutRequest, PayoutResult, PayoutStatus,
};
/// Wise Platform driver.
#[derive(Clone, Debug, Default)]
pub struct WiseDriver {
/// Operator's Wise profile id (numeric, opaque).
pub profile_id: u64,
/// Quote id obtained from a prior `POST /v3/profiles/.../quotes`.
pub quote_uuid: String,
}
#[derive(Debug, Serialize)]
struct TransferBody<'a> {
#[serde(rename = "targetAccount")]
target_account: u64,
#[serde(rename = "quoteUuid")]
quote_uuid: &'a str,
#[serde(rename = "customerTransactionId")]
customer_transaction_id: &'a str,
details: TransferDetails<'a>,
}
#[derive(Debug, Serialize)]
struct TransferDetails<'a> {
reference: &'a str,
}
impl Payout for WiseDriver {
fn rail(&self) -> &'static str {
"wise"
}
fn submit(&self, req: &PayoutRequest) -> Result<PayoutResult> {
let recipient_id = match &req.beneficiary.account {
BeneficiaryAccount::WiseRecipientId(id) => *id,
_ => return Err(Error::UnsupportedMethod { rail: "wise" }),
};
if !req.amount.is_positive() {
return Err(Error::LimitViolation {
rail: "wise",
detail: "amount must be positive".to_string(),
});
}
let body = TransferBody {
target_account: recipient_id,
quote_uuid: &self.quote_uuid,
customer_transaction_id: &req.idempotency_key,
details: TransferDetails {
reference: req.memo.as_deref().unwrap_or(&req.idempotency_key),
},
};
let json = serde_json::to_vec(&body).map_err(|e| Error::DriverValidation(e.to_string()))?;
Ok(PayoutResult {
idempotency_key: req.idempotency_key.clone(),
payout_id: Uuid::now_v7().to_string(),
status: PayoutStatus::PreparedOffline,
raw_status: None,
reason_code: None,
reason_text: None,
rail_txn_id: None,
settled_amount: Some(req.amount),
wire_payload: Some(json),
})
}
fn status(&self, _payout_id: &str) -> Result<PayoutResult> {
Err(Error::DriverValidation(
"Wise status query requires GET /v1/transfers/{id} via live client".to_string(),
))
}
}