import {z} from 'zod' export type ScopeItem = {providerId: string, accountId?: string | undefined}; export type TransferRail = 'BLOCKCHAIN' | 'INTERNAL' | 'SWIFT' | 'IBAN' | 'US_WIRE' | 'ACH' | 'SEPA' | 'SPEI' | 'PIX' | 'LBT' | 'MOMO' | 'CHAPS' | 'PAYID' | 'INTERAC' | 'INTERNAL_TRANSFER' | 'CIPS' | 'NEQUI' | 'FPS_UK' | 'FPS_HK' | 'INSTA_PAY' | 'PESONET'; export type DVPSettlementType = 'DVP'; export type PlatformPeerType = 'VAULT_ACCOUNT' | 'CONNECTED_ACCOUNT' | 'FIAT_ACCOUNT'; export type PlatformAccount = {type: PlatformPeerType, accountId: string}; export type ExternalAccountType = 'EXTERNAL'; export type ExternalAccountMobileMoneyType = 'MOBILE_MONEY_RAIL'; export type MobilePhoneNumber = string; export type ExternalAccountMobileMoneyProvider = 'M_PESA' | 'AIRTEL' | 'MTN' | 'TIGO' | 'ORANGE'; export type ExternalAccountMobileMoney = {type: ExternalAccountMobileMoneyType, mobilePhoneNumber: MobilePhoneNumber, provider: ExternalAccountMobileMoneyProvider, email: string, successRedirectUrl?: string | undefined}; export type ExternalAccountLocalBankAfrica = {successRedirectUrl?: string | undefined}; export type ExternalAccountSenderInformation = ExternalAccountMobileMoney | ExternalAccountLocalBankAfrica; export type ExternalAccount = {type: ExternalAccountType, senderInformation?: ExternalAccountSenderInformation | undefined}; export type SettlementSourceAccount = PlatformAccount | ExternalAccount; export type PeerType = 'VAULT_ACCOUNT' | 'EXCHANGE_ACCOUNT' | 'UNMANAGED_WALLET' | 'FIAT_ACCOUNT' | 'CONNECTED_ACCOUNT'; export type InternalReference = {type: PeerType, accountId: string}; export type OneTimeAddressPeerType = 'ONE_TIME_ADDRESS'; export type OneTimeAddressReference = {type: OneTimeAddressPeerType, address: string, tag?: string | undefined}; export type AccountReference = InternalReference | OneTimeAddressReference; export type DVPSettlement = {type: DVPSettlementType, sourceAccount: SettlementSourceAccount, destinationAccount: AccountReference}; export type PricingSide = 'BUY' | 'SELL' | 'BOTH'; export type PersonalEntityTypeEnum = 'INDIVIDUAL'; export type ParticipantRelationshipType = 'FirstParty' | 'SecondParty' | 'ThirdParty'; export type PostalAddress = {streetName: string, buildingNumber: string, postalCode: string, city: string, subdivision: string, district: string, country: string}; export type PersonalIdentificationType = 'NIN_SLIP_CARD' | 'BVN' | 'ID_CARD' | 'ID_BOOK' | 'NATIONAL_ID' | 'ALIEN_CARD' | 'PASSPORT' | 'ECOWAS_CARD' | 'RWANDA_CARD' | 'DRIVERS_LICENSE' | 'RESIDENT_CARD' | 'VOTER_ID' | 'HEALTH_INSURANCE_ID' | 'GHANA_CARD' | 'REFUGEE_ID' | 'SSNIT' | 'NON_CITIZEN_ID' | 'NATIONAL_REGISTRATION_CARD'; export type PersonalIdentificationDocument = {id?: string | undefined, type?: PersonalIdentificationType | undefined, expirationDate?: string | undefined}; export type PersonalIdentification = {externalReferenceId: string, entityType: PersonalEntityTypeEnum, participantRelationshipType: ParticipantRelationshipType, fullName: {firstName: string, lastName: string}, dateOfBirth: string, postalAddress: PostalAddress, email?: string | undefined, phone?: MobilePhoneNumber | undefined, idNumber?: string | undefined, idType?: PersonalIdentificationType | undefined, additionalIdNumber?: string | undefined, additionalIdType?: PersonalIdentificationType | undefined, nationality?: string | undefined, identificationDocuments?: Array | undefined}; export type BusinessEntityTypeEnum = 'BUSINESS'; export type BusinessIdentification = {externalReferenceId: string, entityType: BusinessEntityTypeEnum, participantRelationshipType: ParticipantRelationshipType, businessName: string, registrationNumber: string, postalAddress: PostalAddress, email?: string | undefined, phone?: MobilePhoneNumber | undefined, dateOfRegistration?: string | undefined, countryOfRegistration?: string | undefined}; export type Identification = PersonalIdentification | BusinessIdentification; export type ParticipantsIdentification = {originator?: Identification | undefined, beneficiary?: Identification | undefined}; export type CreateQuote = {scope: Array, baseAssetId: string, baseAssetRail?: TransferRail | undefined, quoteAssetId: string, quoteAssetRail?: TransferRail | undefined, baseAmount: string, slippageBps?: number | undefined, settlement?: DVPSettlement | undefined, side?: PricingSide | undefined, participantsIdentification?: ParticipantsIdentification | undefined}; export type CreateApiTradingQuotesArgs = {'Idempotency-Key'?: string | undefined, body: CreateQuote}; export type CreateApiTradingQuotesOptions = {baseUrl?: string | undefined, fetch?: typeof fetch | undefined}; export const accountAccessResponse = z.object({type: z.literal("PROVIDER_ACCOUNT"), providerId: z.string().optional(), accountId: z.string()}); export const directAccessResponse = z.object({type: z.literal("PROVIDER"), providerId: z.string(), subProviders: z.array(z.string()).optional()}); export const accessTypeResponse = z.discriminatedUnion("type", [accountAccessResponse, directAccessResponse]); export const transferRail = z.enum(["BLOCKCHAIN", "INTERNAL", "SWIFT", "IBAN", "US_WIRE", "ACH", "SEPA", "SPEI", "PIX", "LBT", "MOMO", "CHAPS", "PAYID", "INTERAC", "INTERNAL_TRANSFER", "CIPS", "NEQUI", "FPS_UK", "FPS_HK", "INSTA_PAY", "PESONET"]); export const executionStepType = z.enum(["APPROVE", "PERMIT", "CONTRACT_CALL", "EXECUTE", "SETTLEMENT", "DELIVERY"]); export const feeTypeEnum = z.enum(["ORDER", "NETWORK", "SPREAD", "REBATE"]); export const fixedAmountTypeEnum = z.literal("FIXED"); export const fee = z.union([z.object({feeType: feeTypeEnum, assetId: z.string(), amountType: fixedAmountTypeEnum, amount: z.string()}), z.object({feeType: feeTypeEnum, assetId: z.string(), amountType: z.literal("BPS"), amount: z.number().gte(0).lte(10000)})]); export const quoteExecutionStep = z.object({type: executionStepType, fee: fee.optional()}); export const side = z.enum(["BUY", "SELL"]); export const quote = z.object({via: accessTypeResponse, id: z.string(), quoteAssetId: z.string(), quoteAssetRail: transferRail.optional(), baseAssetId: z.string(), baseAssetRail: transferRail.optional(), baseAmount: z.string(), quoteAmount: z.string(), priceImpact: z.number().optional(), quoteMinAmount: z.string().optional(), isSlippageApplied: z.boolean().optional(), executionSteps: z.array(quoteExecutionStep).optional(), generalFees: z.array(fee).optional(), side: side, expiresAt: z.string(), requiredParticipantsIdentificationOnOrder: z.string().optional(), type: z.enum(["COMMITTED", "INDICATIVE"])}); export const tradingErrorSchema = z.object({message: z.string(), code: z.number(), descriptor: z.string().optional()}); export const scopeItemFailure = z.object({providerId: z.string(), accountId: z.string().optional(), error: tradingErrorSchema}); export const quotesResponse = z.object({quotes: z.array(quote), quoteFailures: z.array(scopeItemFailure)}); 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 a quote * * POST /trading/quotes */ export const createApiTradingQuotes = async (args: CreateApiTradingQuotesArgs, options?: CreateApiTradingQuotesOptions) => { const {baseUrl = "https://api.fireblocks.io/v1", fetch: fetchFn = fetch} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('content-type', "application/json") headers.set('accept', "application/json") if (args['Idempotency-Key'] != null) headers.set("Idempotency-Key", String(args['Idempotency-Key'])) const url = `${baseUrl.replace(/\/+$/, '')}/trading/quotes${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(quotesResponse, await res.json()) };