import {z} from 'zod' export type AmountType = 'exact_input' | 'exact_output'; export type StellarAddress = string; export type TronAddress = string; export type TokenTransferSource = {amount?: string | undefined, chain: string, asset: string} | {amount?: string | undefined, chain: string, asset_address: StellarAddress | string | string | TronAddress}; export type TokenTransferDestination = {address: StellarAddress | string | string | TronAddress, asset?: string | undefined, chain?: string | undefined}; export type TransferInitiationChannel = 'p2p_mobile_payment' | 'other_mobile_payment' | 'other' | string; export type TransferInitiationSubchannel = 'remote' | 'non_remote' | string; export type TransferScaOutcome = 'sca_used' | 'payment_to_self' | 'trusted_beneficiaries' | 'recurring_transaction' | 'contactless_low_value' | 'unattended_terminal_for_transport' | 'low_value' | 'secure_corporate_payment' | 'transaction_risk_analysis' | 'merchant_initiated_transaction' | 'not_applicable' | 'other' | string; export type TransferScaAuthFactorCategory = 'knowledge' | 'possession' | 'inherence' | string; export type TransferScaAuthFactor = {category: TransferScaAuthFactorCategory, authenticated_at: string, reference: string}; export type TransferScaAttestation = {outcome: TransferScaOutcome, auth_factors?: Array | undefined}; export type TransferInitiationAttestations = {sca: TransferScaAttestation}; export type TransferInitiation = {channel: TransferInitiationChannel, subchannel: TransferInitiationSubchannel, attestations: TransferInitiationAttestations}; export type TransferCustodyOptions = {initiation: TransferInitiation}; export type WalletActionNonce = string; export type TransferRequestBody = {amount?: string | undefined, amount_type?: AmountType | undefined, source: TokenTransferSource, destination: TokenTransferDestination, slippage_bps?: number | undefined, fee_configuration?: {type: 'total_fee_bps', value: number} | undefined, custody_options?: TransferCustodyOptions | undefined, nonce?: WalletActionNonce | undefined, reference_id?: string | undefined}; export type CreateApiV1IntentsWalletsWalletIdTransferArgs = {wallet_id: string, 'privy-app-id': string, 'privy-request-expiry'?: string | undefined, body: TransferRequestBody}; export type CreateApiV1IntentsWalletsWalletIdTransferOptions = {baseUrl?: string | undefined, appSecretAuth?: {username: string, password: string} | undefined, fetch?: typeof fetch | undefined}; export const intentAuthorizationKeyQuorumMember = z.discriminatedUnion("type", [z.object({type: z.literal("user"), user_id: z.string(), signed_at: z.number().nullable()}), z.object({type: z.literal("key"), public_key: z.string(), signed_at: z.number().nullable()})]); export const intentAuthorizationMember = z.discriminatedUnion("type", [z.object({type: z.literal("user"), user_id: z.string(), signed_at: z.number().nullable()}), z.object({type: z.literal("key"), public_key: z.string(), signed_at: z.number().nullable()}), z.object({type: z.literal("key_quorum"), key_quorum_id: z.string(), display_name: z.string().optional(), threshold_met: z.boolean(), threshold: z.number(), members: z.array(intentAuthorizationKeyQuorumMember)})]); export const intentAuthorization = z.object({members: z.array(intentAuthorizationMember), threshold: z.number(), display_name: z.string().optional()}); export const intentStatus = z.enum(["pending", "processing", "executed", "failed", "expired", "rejected", "dismissed"]); export const amountType = z.enum(["exact_input", "exact_output"]); export const stellarAddress = z.string(); export const tronAddress = z.string(); export const tokenTransferSource = z.union([z.object({amount: z.string().max(100).optional(), chain: z.string(), asset: z.string()}), z.object({amount: z.string().max(100).optional(), chain: z.string(), asset_address: z.union([stellarAddress, z.string(), z.string(), tronAddress])})]); export const tokenTransferDestination = z.object({address: z.union([stellarAddress, z.string(), z.string(), tronAddress]), asset: z.string().optional(), chain: z.string().optional()}); export const transferInitiationChannel = z.union([z.enum(["p2p_mobile_payment", "other_mobile_payment", "other"]), z.string()]); export const transferInitiationSubchannel = z.union([z.enum(["remote", "non_remote"]), z.string()]); export const transferScaOutcome = z.union([z.enum(["sca_used", "payment_to_self", "trusted_beneficiaries", "recurring_transaction", "contactless_low_value", "unattended_terminal_for_transport", "low_value", "secure_corporate_payment", "transaction_risk_analysis", "merchant_initiated_transaction", "not_applicable", "other"]), z.string()]); export const transferScaAuthFactorCategory = z.union([z.enum(["knowledge", "possession", "inherence"]), z.string()]); export const transferScaAuthFactor = z.object({category: transferScaAuthFactorCategory, authenticated_at: z.string(), reference: z.string().min(1).max(64)}); export const transferScaAttestation = z.object({outcome: transferScaOutcome, auth_factors: z.array(transferScaAuthFactor).optional()}); export const transferInitiationAttestations = z.object({sca: transferScaAttestation}); export const transferInitiation = z.object({channel: transferInitiationChannel, subchannel: transferInitiationSubchannel, attestations: transferInitiationAttestations}); export const transferCustodyOptions = z.object({initiation: transferInitiation}); export const walletActionNonce = z.string().min(24).max(255); export const transferRequestBody = z.object({amount: z.string().max(100).optional(), amount_type: amountType.optional(), source: tokenTransferSource, destination: tokenTransferDestination, slippage_bps: z.number().int().gte(0).lte(10000).optional(), fee_configuration: z.object({type: z.literal("total_fee_bps"), value: z.number().int().gte(0).lte(10000)}).optional(), custody_options: transferCustodyOptions.optional(), nonce: walletActionNonce.optional(), reference_id: z.string().min(1).max(64).optional()}); export const walletChainType = z.enum(["ethereum", "solana", "cosmos", "stellar", "sui", "aptos", "movement", "tron", "bitcoin-segwit", "bitcoin-taproot", "pearl", "near", "ton", "starknet", "xrpl", "spark"]); export const entityId = z.string().min(1); export const walletEntityType = z.enum(["user", "organization"]); export const walletEntity = z.object({id: entityId, type: walletEntityType}); export const keyQuorumId = z.string(); export const policyInput = z.array(z.string().min(24).max(24)); export const walletAdditionalSignerItem = z.object({signer_id: keyQuorumId, override_policy_ids: policyInput.optional()}); export const walletAdditionalSigner = z.array(walletAdditionalSignerItem); export const attachedWalletAutomation = z.object({id: z.string(), enabled: z.boolean()}); export const walletCustodian = z.object({provider: z.string(), provider_user_id: z.string()}); export const failureReason = z.object({message: z.string(), details: z.unknown()}); export const currencyAmount = z.object({value: z.string(), currency: z.literal("usd")}); export const externalTransactionStepProvider = z.union([z.enum(["relay", "tempo_routes", "bridge"]), z.string()]); export const hex = z.string().max(300002); export const tmmfOrderType = z.enum(["primary", "secondary"]); export const tmmfCurrency = z.string().min(1).max(16); export const tmmfDecimalAmount = z.string().min(1).max(64); export const tmmfFund = z.string().min(1).max(16); export const wisdomTreeSettlementRequestedAmount = z.union([z.object({deposit_asset: tmmfCurrency, amount: tmmfDecimalAmount}), z.object({fund: tmmfFund, amount: tmmfDecimalAmount})]); export const wisdomTreeSettlementReceivedAmount = z.union([z.object({fund: tmmfFund, amount: tmmfDecimalAmount}), z.object({withdraw_asset: tmmfCurrency, amount: tmmfDecimalAmount})]); export const walletActionStep = z.discriminatedUnion("type", [z.object({type: z.literal("evm_transaction"), failure_reason: failureReason.optional(), finalized: z.boolean().optional(), gas_credits_charged: currencyAmount.nullable().optional(), gas_credits_charged_usd: z.string().nullable().optional(), status: z.enum(["preparing", "queued", "pending", "retrying", "confirmed", "rejected", "reverted", "replaced", "abandoned"]), caip2: z.string(), transaction_hash: z.string().nullable()}), z.object({type: z.literal("evm_user_operation"), failure_reason: failureReason.optional(), finalized: z.boolean().optional(), gas_credits_charged: currencyAmount.nullable().optional(), gas_credits_charged_usd: z.string().nullable().optional(), status: z.enum(["preparing", "queued", "pending", "retrying", "confirmed", "rejected", "reverted", "replaced", "abandoned"]), user_operation_hash: z.string().nullable(), bundle_transaction_hash: z.string().nullable(), caip2: z.string(), entrypoint_version: z.enum(["0.6", "0.7", "0.8", "0.9"])}), z.object({type: z.literal("svm_transaction"), failure_reason: failureReason.optional(), finalized: z.boolean().optional(), gas_credits_charged: currencyAmount.nullable().optional(), gas_credits_charged_usd: z.string().nullable().optional(), status: z.enum(["preparing", "queued", "pending", "confirmed", "rejected", "reverted", "failed"]), caip2: z.string(), transaction_signature: z.string().nullable()}), z.object({type: z.literal("tvm_transaction"), failure_reason: failureReason.optional(), gas_credits_charged: currencyAmount.nullable().optional(), gas_credits_charged_usd: z.string().nullable().optional(), status: z.enum(["preparing", "queued", "pending", "confirmed", "rejected", "reverted", "failed"]), caip2: z.string(), transaction_id: z.string().nullable()}), z.object({type: z.literal("external_transaction"), failure_reason: failureReason.optional(), status: z.enum(["preparing", "queued", "pending", "confirmed", "rejected", "failed"]), provider: externalTransactionStepProvider.nullable()}), z.object({type: z.literal("custodian_transaction"), failure_reason: failureReason.optional(), status: z.enum(["preparing", "queued", "custodian_reviewing", "pending", "confirmed", "rejected", "failed"]), custodian: z.string(), transaction_hash: z.string().nullable()}), z.object({type: z.literal("tempo_zone_settlement"), failure_reason: failureReason.optional(), caip2: z.string().nullable(), transaction_hash: hex.nullable(), status: z.enum(["preparing", "queued", "pending", "confirmed", "rejected", "failed"])}), z.object({type: z.literal("wisdomtree_settlement"), failure_reason: failureReason.optional(), status: z.union([z.enum(["preparing", "queued", "pending", "confirmed", "rejected", "failed"]), z.string()]), caip2: z.string().nullable(), order_type: tmmfOrderType.nullable(), requested: wisdomTreeSettlementRequestedAmount.nullable(), received: wisdomTreeSettlementReceivedAmount.nullable(), settlement_transaction_hash: z.string().nullable()})]); export const feeLineItem = z.discriminatedUnion("type", [z.object({type: z.literal("relayer"), recipient: z.union([stellarAddress, z.string(), z.string(), tronAddress]).optional(), amount: z.string()}), z.object({type: z.literal("privy"), recipient: z.union([stellarAddress, z.string(), z.string(), tronAddress]).optional(), amount: z.string()}), z.object({type: z.literal("developer"), recipient: z.union([stellarAddress, z.string(), z.string(), tronAddress]).optional(), amount: z.string()})]); export const gas = z.object({base_amount: z.string(), amount: z.string(), gas_asset: z.string()}); export const transferActionResponse = z.object({id: z.string(), status: z.enum(["pending", "succeeded", "rejected", "failed"]), wallet_id: z.string(), created_at: z.string(), reference_id: z.string().nullable().optional(), failure_reason: z.object({message: z.string(), details: z.unknown()}).optional(), steps: z.array(walletActionStep).optional(), type: z.literal("transfer"), amount_type: amountType.optional(), source_asset: z.string().optional(), source_asset_address: z.string().optional(), source_asset_decimals: z.number().int().optional(), source_amount: z.string().optional(), source_chain: z.string(), destination_address: z.string(), destination_asset: z.string().optional(), destination_chain: z.string().optional(), destination_amount: z.string().nullable(), estimated_fees: z.array(feeLineItem).nullable().optional(), estimated_gas: gas.nullable().optional(), fees: z.array(feeLineItem).nullable().optional(), gas: gas.nullable().optional()}); export const transferIntentResponse = z.object({intent_id: z.string(), created_by_display_name: z.string(), created_by_id: z.string().optional(), created_at: z.number(), resource_id: z.string(), authorization_details: z.array(intentAuthorization), status: intentStatus, custom_expiry: z.boolean(), expires_at: z.number(), rejected_at: z.number().optional(), dismissed_at: z.number().optional(), dismissal_reason: z.string().optional(), intent_type: z.literal("TRANSFER"), request_details: z.object({method: z.literal("POST"), url: z.string(), body: transferRequestBody}), current_resource_data: z.object({id: z.string(), address: z.string(), display_name: z.string().optional(), external_id: z.string().max(64).optional(), public_key: z.string().optional(), created_at: z.number(), chain_type: walletChainType, policy_ids: z.array(z.string()), owner_id: z.string().nullable(), parent_wallet_id: z.string().nullable().optional(), entity: walletEntity.nullable().optional(), additional_signers: walletAdditionalSigner, exported_at: z.number().nullable(), imported_at: z.number().nullable(), authorization_threshold: z.number().optional(), automations: z.array(attachedWalletAutomation).optional(), archived_at: z.number().nullable().optional(), chain: z.enum(["base", "solana", "tempo"]).optional(), custody: walletCustodian.optional()}).optional(), action_result: z.object({status_code: z.number(), executed_at: z.number(), authorized_by_display_name: z.string().optional(), authorized_by_id: z.string().optional(), response_body: transferActionResponse}).optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Create transfer intent * * POST /v1/intents/wallets/{wallet_id}/transfer */ export const createApiV1IntentsWalletsWalletIdTransfer = async (args: CreateApiV1IntentsWalletsWalletIdTransferArgs, options?: CreateApiV1IntentsWalletsWalletIdTransferOptions) => { const {baseUrl = "https://api.privy.io", fetch: fetchFn = fetch, appSecretAuth} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('content-type', "application/json") headers.set('accept', "application/json") if (args['privy-app-id'] != null) headers.set("privy-app-id", String(args['privy-app-id'])) if (args['privy-request-expiry'] != null) headers.set("privy-request-expiry", String(args['privy-request-expiry'])) if (appSecretAuth !== undefined) headers.set('authorization', `Basic ${btoa(`${appSecretAuth.username}:${appSecretAuth.password}`)}`) const url = `${baseUrl.replace(/\/+$/, '')}/v1/intents/wallets/${encodeURIComponent(String(args.wallet_id))}/transfer${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "POST", headers, body: JSON.stringify(args.body)}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(transferIntentResponse, await res.json()) };