@recoilpay/intent-core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -177,6 +177,106 @@ interface OrderResponse {
177
177
  fillTransaction?: unknown;
178
178
  }
179
179
 
180
+ /** The aggregator's fiat API, as it sends it. See design §12.1. */
181
+ type FiatDirection = 'onramp' | 'offramp';
182
+ type FiatTradeState = 'quoted' | 'awaiting_fiat' | 'awaiting_confirmation' | 'disputed' | 'settled_to_fiat_payer' | 'refunded_to_funder' | 'failed';
183
+ /** One fiat trade. Snake case, as `GET /api/v1/fiat/trades/{id}` returns it. */
184
+ interface FiatTrade {
185
+ id: string;
186
+ quote_id: string;
187
+ solver_id: string;
188
+ direction: FiatDirection;
189
+ /** CAIP-2, e.g. `eip155:84532`. */
190
+ chain: string;
191
+ asset: string;
192
+ asset_minor_units: string;
193
+ fiat_currency: string;
194
+ /** What reaches the payee's account, in minor units (kobo for NGN). */
195
+ fiat_minor_units: string;
196
+ rail: string;
197
+ rate: string;
198
+ fee_minor_units: string;
199
+ user_address: string;
200
+ solver_address: string;
201
+ state: FiatTradeState;
202
+ /** When the party the trade is waiting on runs out of time. */
203
+ deadline_at: string | null;
204
+ resolution_note: string | null;
205
+ payee_commitment: string | null;
206
+ payment_reference: string | null;
207
+ proof_kind: 'counterparty' | 'provider' | 'zktls' | null;
208
+ escrow_tx_hash: string | null;
209
+ release_tx_hash: string | null;
210
+ created_at: string;
211
+ updated_at: string;
212
+ }
213
+ /** `POST /api/v1/fiat/quotes`. */
214
+ interface RankFiatQuotesRequest {
215
+ direction: FiatDirection;
216
+ chain: string;
217
+ asset: string;
218
+ fiatCurrency: string;
219
+ rail: string;
220
+ /** Minor units of the asset, as a whole-number string. */
221
+ assetAmount: string;
222
+ }
223
+ /** One priced offer, best first. Amounts are minor-unit strings. */
224
+ interface RankedFiatQuote {
225
+ quoteId: string;
226
+ solverId: string;
227
+ rate: string;
228
+ assetAmount: string;
229
+ fiatCurrency: string;
230
+ rail: string;
231
+ fiatGross: string;
232
+ fiatFee: string;
233
+ /** What reaches the user's account. */
234
+ fiatNet: string;
235
+ paymentWindowSecs: number;
236
+ expiry: number;
237
+ score: number;
238
+ /** Seal the bank details to this before opening a trade. */
239
+ payeeKey: string;
240
+ }
241
+ interface RankFiatQuotesResponse {
242
+ quotes: RankedFiatQuote[];
243
+ totalConsidered: number;
244
+ }
245
+ /** What the user signs (Permit2 `PermitWitnessTransferFrom`) and the relay submits. */
246
+ interface FiatLockInstructions {
247
+ chainId: number;
248
+ escrow: string;
249
+ tradeId: string;
250
+ lockId: string;
251
+ token: string;
252
+ amount: string;
253
+ counterparty: string;
254
+ nonce: string;
255
+ deadline: number;
256
+ typedData: {
257
+ domain: Record<string, unknown>;
258
+ types: Record<string, ReadonlyArray<{
259
+ name: string;
260
+ type: string;
261
+ }>>;
262
+ primaryType: string;
263
+ message: Record<string, unknown>;
264
+ };
265
+ }
266
+ interface OpenFiatTradeRequest {
267
+ quoteId: string;
268
+ assetAmount: string;
269
+ userAddress: string;
270
+ payee: {
271
+ sealed: unknown;
272
+ commitment: string;
273
+ };
274
+ }
275
+ interface OpenFiatTradeResponse {
276
+ trade: FiatTrade;
277
+ lock: FiatLockInstructions;
278
+ }
279
+
180
280
  /** Production aggregator. Override with `apiUrl` for staging or a local instance. */
181
281
  declare const DEFAULT_API_URL = "https://api.recoilpay.com";
182
282
  interface ApiOptions {
@@ -221,10 +321,22 @@ declare function createApiClient(options?: ApiOptions): {
221
321
  getQuotes: (req: QuoteRequest) => Promise<QuotesResponse>;
222
322
  submitOrder: (req: OrderRequest) => Promise<OrderResponse>;
223
323
  getOrder: (id: string) => Promise<OrderResponse>;
324
+ rankFiatQuotes: (req: RankFiatQuotesRequest) => Promise<RankFiatQuotesResponse>;
325
+ openFiatTrade: (req: OpenFiatTradeRequest) => Promise<OpenFiatTradeResponse>;
326
+ getFiatTrade: (id: string) => Promise<FiatTrade>;
327
+ /** Hand the signed lock to the solver's relay, which submits it and pays the gas. */
328
+ submitFiatLock: (id: string, signature: string) => Promise<FiatTrade>;
329
+ /** Report a lock the user submitted themselves; the aggregator reads it from the chain. */
330
+ reportFiatFunded: (id: string, txHash?: string) => Promise<FiatTrade>;
331
+ /** `signature` is over `fiatConfirmMessage(id)`. */
332
+ confirmFiatTrade: (id: string, signature: string) => Promise<FiatTrade>;
333
+ /** `signature` is over `fiatDisputeMessage(id, reason)`. */
334
+ disputeFiatTrade: (id: string, reason: string, signature: string) => Promise<FiatTrade>;
224
335
  };
225
336
 
226
337
  /** Intent parser types. See docs/intent-parser-design.md. */
227
- type IntentAction = 'swap' | 'send';
338
+ /** `offramp` cashes crypto out to a bank account (fiat ramps, design §5.1). */
339
+ type IntentAction = 'swap' | 'send' | 'offramp';
228
340
  type AmountKind = 'token' | 'usd';
229
341
  /** Output of the grammar parser, before resolution. Raw alias strings as typed. */
230
342
  interface RawIntent {
@@ -236,6 +348,10 @@ interface RawIntent {
236
348
  tokenOut: string | null;
237
349
  chainOut: string | null;
238
350
  recipient: string | null;
351
+ /** offramp only: the currency to receive, as typed ("naira", "NGN"). Bank
352
+ * details are never parsed: they are collected in a form, so they never
353
+ * leave the browser unsealed. */
354
+ fiatCurrency?: string | null;
239
355
  }
240
356
  type ParseResult = {
241
357
  ok: true;
@@ -245,7 +361,7 @@ type ParseResult = {
245
361
  offTemplate: true;
246
362
  message: string;
247
363
  };
248
- type IssueField = 'amount' | 'tokenIn' | 'chainIn' | 'tokenOut' | 'chainOut' | 'recipient';
364
+ type IssueField = 'amount' | 'tokenIn' | 'chainIn' | 'tokenOut' | 'chainOut' | 'recipient' | 'fiatCurrency';
249
365
  type IssueKind = 'missing' | 'unknown' | 'unsupported';
250
366
  interface ValidationIssue {
251
367
  field: IssueField;
@@ -265,7 +381,16 @@ interface ResolvedIntent {
265
381
  recipient: string;
266
382
  }
267
383
 
268
- declare const TEMPLATE_HINT = "Try: swap <amount> <token> on <chain> for <token> on <chain> [to <address>] (e.g. swap 10 USDC on Base for ETH on Arbitrum) OR send <amount> <token> on <chain> to <address>";
384
+ declare const TEMPLATE_HINT = "Try: swap <amount> <token> on <chain> for <token> on <chain> [to <address>] (e.g. swap 10 USDC on Base for ETH on Arbitrum) OR send <amount> <token> on <chain> to <address> OR cash out <amount> <token> on <chain> to <currency> (e.g. cash out 50 USDC on Base to NGN)";
385
+ /** Shown instead of parsing when the text looks like it holds bank details. */
386
+ declare const BANK_DETAILS_HINT = "Leave bank details out of the sentence \u2014 you'll enter them privately on the next step. Try: cash out 50 USDC on Base to NGN";
387
+ /**
388
+ * Parsing sends the text to a third party (Hugging Face), so bank details
389
+ * must never be in it. An account number is a long run of digits: a NUBAN
390
+ * is ten, an IBAN's tail more. Hex addresses are skipped, and amounts this
391
+ * long do not occur in practice.
392
+ */
393
+ declare function looksLikeBankDetails(text: string): boolean;
269
394
  /** Hugging Face's OpenAI-compatible router: the endpoint @huggingface/inference's chatCompletion uses. */
270
395
  declare const HF_ROUTER_URL = "https://router.huggingface.co/v1/chat/completions";
271
396
  declare const DEFAULT_INTENT_MODEL = "Qwen/Qwen2.5-Coder-32B-Instruct";
@@ -370,6 +495,11 @@ interface IntentWallet {
370
495
  to: Address;
371
496
  value: bigint;
372
497
  }): Promise<Hex>;
498
+ /**
499
+ * EIP-191 `personal_sign` of a plain message. Only fiat trades need it (to
500
+ * confirm or dispute), so a wallet used for swaps alone may omit it.
501
+ */
502
+ signMessage?(message: string): Promise<Hex>;
373
503
  }
374
504
  /** Adapt a viem `WalletClient` (with an account attached) to `IntentWallet`. */
375
505
  declare function viemWallet(client: WalletClient): IntentWallet;
@@ -382,6 +512,89 @@ declare function stripDomainType(types: Record<string, ReadonlyArray<{
382
512
  type: string;
383
513
  }>>;
384
514
 
515
+ /**
516
+ * The registry has two layers (see docs/intent-parser-design.md §2):
517
+ * - alias layer (static): maps loose user words → canonical token symbol / chain.
518
+ * - support layer (dynamic): what solvers can actually fill, from the live aggregator.
519
+ *
520
+ * A word can be a token OR a chain depending on slot (e.g. "eth", "sol"); the grammar
521
+ * decides which table to consult by position, so the same word appears in both.
522
+ */
523
+ interface CanonicalChain {
524
+ id: number;
525
+ name: string;
526
+ }
527
+ declare const TOKEN_ALIASES: Record<string, string>;
528
+ declare const CHAIN_ALIASES: Record<string, CanonicalChain>;
529
+ declare function normalizeToken(raw: string | null): string | null;
530
+ declare function normalizeChain(raw: string | null): CanonicalChain | null;
531
+ interface SupportedSet {
532
+ chains: Set<number>;
533
+ assets: Map<string, SolverAsset>;
534
+ }
535
+ declare const EMPTY_SUPPORTED: SupportedSet;
536
+ declare function buildSupportedSet(assets: SolverAsset[]): SupportedSet;
537
+ declare function findAsset(set: SupportedSet, chainId: number, symbol: string): SolverAsset | undefined;
538
+
539
+ /**
540
+ * The fiat corridors this kit knows how to collect payee details for.
541
+ *
542
+ * A corridor is a currency, the rail payouts go over, and how its banks are
543
+ * named. Bank codes matter beyond display: the payee commitment covers the
544
+ * bank code, and a Tier 1 proof recomputes it from the code the payment
545
+ * provider reports. So the codes here must be the ones the solvers' provider
546
+ * uses — Paystack's, for the Phase 1 pilot.
547
+ */
548
+ interface FiatBank {
549
+ code: string;
550
+ name: string;
551
+ }
552
+ interface FiatCorridor {
553
+ currency: string;
554
+ rail: string;
555
+ /** Minor units per major unit, as a power of ten (kobo: 2). */
556
+ decimals: number;
557
+ symbol: string;
558
+ /** What the account number is called, for a form label. */
559
+ accountLabel: string;
560
+ /** Checks shape only; the solver checks the account exists. */
561
+ accountPattern: RegExp;
562
+ banks: FiatBank[];
563
+ }
564
+ /**
565
+ * Nigerian banks with Paystack's codes (CBN codes for commercial banks,
566
+ * Paystack's own for fintechs). Not exhaustive. Checked against Paystack's
567
+ * `GET /bank?country=nigeria` on 2026-10-05; re-check when adding banks or a
568
+ * provider: a wrong code here makes a correct payout fail its commitment
569
+ * check.
570
+ */
571
+ declare const NG_BANKS: FiatBank[];
572
+ declare const FIAT_CORRIDORS: Record<string, FiatCorridor>;
573
+ declare const FIAT_CURRENCY_ALIASES: Record<string, string>;
574
+ declare function normalizeFiatCurrency(raw: string | null | undefined): string | null;
575
+ /** Minor units → "151,543.85". */
576
+ declare function formatFiat(minor: string | bigint, corridor: Pick<FiatCorridor, 'decimals'>): string;
577
+
578
+ /** An off-ramp, resolved: everything needed to ask for fiat quotes. */
579
+ interface OfframpRequest {
580
+ chainId: number;
581
+ chainName: string;
582
+ /** Symbol, as fiat quotes name assets (`USDC`). */
583
+ asset: string;
584
+ token: string;
585
+ decimals: number;
586
+ /** Base units of `asset`. */
587
+ amount: bigint;
588
+ corridor: FiatCorridor;
589
+ }
590
+ /**
591
+ * Validate a parsed `offramp` intent. Like `validateIntent`, it collects
592
+ * every problem at once.
593
+ */
594
+ declare function validateOfframp(raw: RawIntent, supported: SupportedSet): ValidationIssue[];
595
+ /** PRECONDITION: `validateOfframp(raw, supported)` returned no issues. */
596
+ declare function resolveOfframp(raw: RawIntent, supported: SupportedSet): OfframpRequest;
597
+
385
598
  /**
386
599
  * Where the flow is.
387
600
  *
@@ -395,9 +608,12 @@ declare function stripDomainType(types: Record<string, ReadonlyArray<{
395
608
  * switchingChain / approving / signing / submitting confirm() progress
396
609
  * tracking order submitted, polling its status
397
610
  * done every intent reached a terminal status
611
+ * offramp a cash-out to a bank: `offramp` is resolved; hand it to
612
+ * an off-ramp session (`createOfframpSession`), which
613
+ * collects the bank details and runs the trade
398
614
  * error something threw; see `error`
399
615
  */
400
- type IntentPhase = 'idle' | 'parsing' | 'offTemplate' | 'invalid' | 'needsWallet' | 'quoting' | 'quoted' | 'switchingChain' | 'approving' | 'signing' | 'submitting' | 'tracking' | 'done' | 'error';
616
+ type IntentPhase = 'idle' | 'parsing' | 'offTemplate' | 'invalid' | 'needsWallet' | 'quoting' | 'quoted' | 'switchingChain' | 'approving' | 'signing' | 'submitting' | 'tracking' | 'done' | 'offramp' | 'error';
401
617
  /** What a confirm card shows: human amounts and names, no addresses or base units. */
402
618
  interface IntentPreview {
403
619
  action: 'swap' | 'send';
@@ -442,6 +658,8 @@ interface IntentState {
442
658
  /** Multi-intent sentences run one after another. */
443
659
  queueIndex: number;
444
660
  queueTotal: number;
661
+ /** Phase `offramp` only. */
662
+ offramp: OfframpRequest | null;
445
663
  }
446
664
  interface IntentSession {
447
665
  getState(): IntentState;
@@ -484,30 +702,6 @@ declare function isTerminalStatus(status: OrderStatus): boolean;
484
702
  */
485
703
  declare function createIntentSession(options?: SessionOptions): IntentSession;
486
704
 
487
- /**
488
- * The registry has two layers (see docs/intent-parser-design.md §2):
489
- * - alias layer (static): maps loose user words → canonical token symbol / chain.
490
- * - support layer (dynamic): what solvers can actually fill, from the live aggregator.
491
- *
492
- * A word can be a token OR a chain depending on slot (e.g. "eth", "sol"); the grammar
493
- * decides which table to consult by position, so the same word appears in both.
494
- */
495
- interface CanonicalChain {
496
- id: number;
497
- name: string;
498
- }
499
- declare const TOKEN_ALIASES: Record<string, string>;
500
- declare const CHAIN_ALIASES: Record<string, CanonicalChain>;
501
- declare function normalizeToken(raw: string | null): string | null;
502
- declare function normalizeChain(raw: string | null): CanonicalChain | null;
503
- interface SupportedSet {
504
- chains: Set<number>;
505
- assets: Map<string, SolverAsset>;
506
- }
507
- declare const EMPTY_SUPPORTED: SupportedSet;
508
- declare function buildSupportedSet(assets: SolverAsset[]): SupportedSet;
509
- declare function findAsset(set: SupportedSet, chainId: number, symbol: string): SolverAsset | undefined;
510
-
511
705
  /**
512
706
  * Validate a parsed intent against the live support set. Collects EVERY problem
513
707
  * (missing / unknown / unsupported) so the user can fix them in one edit.
@@ -620,4 +814,235 @@ declare function buildPermit2ApproveRequest(token: Address, amount?: bigint): {
620
814
  args: readonly [`0x${string}`, bigint];
621
815
  };
622
816
 
623
- export { type AggregationMetadata, type AmountKind, type ApiClient, ApiError, type ApiOptions, type AssetAmount, type AssetLockReference, CHAIN_ALIASES, type CanonicalChain, type ChainInfo, type ChainReader, DEFAULT_API_URL, DEFAULT_INTENT_MODEL, EMPTY_SUPPORTED, HF_ROUTER_URL, type Input, type IntentAction, type IntentPhase, type IntentPreview, type IntentRequest, type IntentSession, type IntentState, type IntentWallet, type InteropAddress, type IssueField, type IssueKind, MAX_UINT256, type Order, type OrderPayload, type OrderRequest, type OrderResponse, type OrderStatus, type OriginSubmission, type Output, PERMIT2_ADDRESS, type ParseResult, type ParserOptions, type Quote, type QuotePreview, type QuoteRequest, type QuotesResponse, type RawIntent, type ResolvedIntent, SOLANA_CHAIN_IDS, type SessionOptions, type Settlement, type Solver, type SolverAsset, type SolverOptions, type SolverSupportedAssets, type SolversResponse, type SupportedSet, TEMPLATE_HINT, TOKEN_ALIASES, type TypedDataSigner, type ValidationIssue, buildOrderRequest, buildPermit2ApproveRequest, buildQuoteRequest, buildSupportedSet, chainName, createApiClient, createIntentSession, explorerUrl, extractIntents, findAsset, getPermit2Allowance, hasPermit2Allowance, interopAddress, isTerminalStatus, normalizeChain, normalizeToken, parseIntent, resolveIntent, rpcReaders, signQuote, stripDomainType, validateIntent, viemWallet };
817
+ /**
818
+ * Payee bank details for a fiat off-ramp: canonical form, commitment, and
819
+ * sealing to the matched solver.
820
+ *
821
+ * Bank details are personal data. They should reach exactly one party — the
822
+ * solver who pays out — and never a chain, a log, or the aggregator's
823
+ * database in readable form. So the client:
824
+ *
825
+ * 1. normalises the account identity (`rail`, `bank_code`, `account`) into
826
+ * one canonical byte string;
827
+ * 2. commits to it as `keccak256(canonical ‖ salt)` with a fresh 32-byte
828
+ * salt — the aggregator stores only this;
829
+ * 3. seals the identity, the account holder's name and the salt to the
830
+ * solver's key, in the same envelope gift cards use.
831
+ *
832
+ * # Why the name is not in the commitment
833
+ *
834
+ * A Tier 1 proof is a provider's payout event, and the aggregator checks it
835
+ * by recomputing the commitment from the account the event names. Providers
836
+ * report account numbers and bank codes reliably but names in their own
837
+ * format ("OSAHON GINO" for "Gino Osahon"), so a commitment over the name
838
+ * would reject honest payouts. The name is sealed for the solver's name
839
+ * check, which is where it is actually used.
840
+ *
841
+ * # Why the commitment is salted
842
+ *
843
+ * An account number is short: a Nigerian NUBAN is ten digits with a check
844
+ * digit, about a billion per bank. Unsalted, anyone holding the database
845
+ * could hash every one in minutes and read every payee's account back out of
846
+ * its commitment. The salt travels inside the envelope, so the solver can
847
+ * attach it to a proof and the user can reveal it in a dispute.
848
+ *
849
+ * # Canonical form
850
+ *
851
+ * Each identity field is normalised and then restricted to a plain character
852
+ * set, so the canonical JSON needs no escaping and is byte-identical in every
853
+ * language. The Rust side (`oif_types::fiat_trades::payee`) implements the
854
+ * same rules, and both are tested against `test-vectors/fiat-payee.json` at
855
+ * the repository root.
856
+ */
857
+
858
+ /** Payee details as a user enters them. */
859
+ interface FiatPayee {
860
+ /** Stable `country.rail` id, e.g. `ng.nip`, `eu.sepa_instant`, `ke.mpesa`. */
861
+ rail: string;
862
+ /** Bank or institution code, e.g. a NIP bank code or a BIC. Empty for rails
863
+ * that address an account directly, such as mobile money. */
864
+ bankCode: string;
865
+ /** Account number, IBAN, or mobile money number. */
866
+ account: string;
867
+ /** The account holder's name, for the solver's name check. */
868
+ name: string;
869
+ }
870
+ /** The fields that identify where the money goes, normalised. */
871
+ interface PayeeIdentity {
872
+ rail: string;
873
+ bank_code: string;
874
+ account: string;
875
+ }
876
+ /** The sealed envelope. Same shape as the aggregator's `SealedCode`. */
877
+ interface SealedPayee {
878
+ v: 1;
879
+ alg: 'ECIES-secp256k1-AES256GCM';
880
+ epk: Hex;
881
+ iv: Hex;
882
+ ct: Hex;
883
+ tag: Hex;
884
+ }
885
+ /** What the solver recovers on opening an envelope. */
886
+ interface OpenedPayee {
887
+ identity: PayeeIdentity;
888
+ name: string;
889
+ salt: Hex;
890
+ }
891
+ /** Normalise and validate the account identity. Throws on anything that
892
+ * would not be safe to commit to.
893
+ *
894
+ * - ASCII whitespace (space, tab, CR, LF) is removed from every field, and
895
+ * `-` from the account — so an IBAN written in groups of four and the
896
+ * same IBAN written solid are one account.
897
+ * - The character set is checked *before* case is changed, and only ASCII
898
+ * is accepted. Unicode case mapping would otherwise turn `ß` into `SS`
899
+ * or the Kelvin sign into `k`, quietly making a different account; and
900
+ * "whitespace" means different characters in different languages, so
901
+ * only the ASCII set is stripped and anything else is rejected. */
902
+ declare function payeeIdentity(payee: Pick<FiatPayee, 'rail' | 'bankCode' | 'account'>): PayeeIdentity;
903
+ /** The canonical bytes, as a string: compact JSON with keys in sorted
904
+ * order. The character sets enforced by [`payeeIdentity`] mean no value
905
+ * ever needs escaping. */
906
+ declare function canonicalPayeeIdentity(identity: PayeeIdentity): string;
907
+ /** `keccak256(canonical ‖ salt)`, `0x`-hex — what the aggregator stores. */
908
+ declare function payeeCommitment(identity: PayeeIdentity, salt: Hex): Hex;
909
+ /** Seal `payee` to the matched solver's encryption key.
910
+ *
911
+ * Returns the envelope and commitment to send to the aggregator, and the
912
+ * salt for the user to keep: in a dispute, the details plus the salt are
913
+ * what prove which account the solver was told to pay.
914
+ *
915
+ * @param solverPubkey The solver's 65-byte uncompressed secp256k1 key.
916
+ * @param salt Only for test vectors. Omit it: a fresh random salt is what
917
+ * makes the commitment unguessable. */
918
+ declare function sealPayee(payee: FiatPayee, solverPubkey: Hex, salt?: Hex): {
919
+ sealed: SealedPayee;
920
+ commitment: Hex;
921
+ salt: Hex;
922
+ };
923
+ /** Open an envelope as the solver, and check it against the trade's
924
+ * commitment.
925
+ *
926
+ * `expectedCommitment` is required, not optional: a solver must never pay
927
+ * out to details that do not match what the aggregator recorded. Otherwise
928
+ * a user could seal one account, commit to another, get paid, and then
929
+ * dispute the payout as going to "the wrong account".
930
+ *
931
+ * Throws if the envelope was tampered with, was sealed to another key, or
932
+ * does not match the commitment. */
933
+ declare function openPayee(sealed: SealedPayee, solverPrivkey: Hex, expectedCommitment: Hex): OpenedPayee;
934
+
935
+ /**
936
+ * Where an off-ramp is.
937
+ *
938
+ * idle nothing asked yet
939
+ * needsWallet ready to quote once a wallet is set
940
+ * quoting / quoted ranking solvers' offers; `preview` is ready
941
+ * switchingChain / approving / opening / signing / locking confirm() progress
942
+ * awaitingFiat the USDC is locked; the solver is paying out
943
+ * awaitingConfirmation the solver says it paid: the user checks their bank
944
+ * and confirms or disputes. Nothing releases the
945
+ * escrow until they do (design §6.1)
946
+ * confirming / disputing signing and sending that answer
947
+ * disputed an admin decides; nobody's clock runs
948
+ * settled the solver was paid out of escrow (the user has the fiat)
949
+ * refunded the USDC went back to the user
950
+ * failed the lock never landed; nothing moved
951
+ * error something threw before a trade existed; see `error`
952
+ */
953
+ type OfframpPhase = 'idle' | 'needsWallet' | 'quoting' | 'quoted' | 'switchingChain' | 'approving' | 'opening' | 'signing' | 'locking' | 'awaitingFiat' | 'awaitingConfirmation' | 'confirming' | 'disputing' | 'disputed' | 'settled' | 'refunded' | 'failed' | 'error';
954
+ /** A confirm card's worth: human amounts, no base units. */
955
+ interface OfframpPreview {
956
+ payAmount: string;
957
+ paySymbol: string;
958
+ chainName: string;
959
+ /** What reaches the bank account. */
960
+ receiveAmount: string;
961
+ currency: string;
962
+ currencySymbol: string;
963
+ fee: string;
964
+ /** Fiat per one unit of the asset. */
965
+ rate: string;
966
+ /** How long the solver has to pay once the USDC is locked. */
967
+ paymentWindowSecs: number;
968
+ quotesConsidered: number;
969
+ }
970
+ interface OfframpState {
971
+ phase: OfframpPhase;
972
+ request: OfframpRequest | null;
973
+ quote: RankedFiatQuote | null;
974
+ preview: OfframpPreview | null;
975
+ /** A Permit2 approval of the asset comes first. */
976
+ needsApproval: boolean;
977
+ trade: FiatTrade | null;
978
+ /**
979
+ * The salt the bank details were committed with. With the details, it
980
+ * proves in a dispute which account the solver was told to pay, so offer
981
+ * it to the user to keep. Only known in the session that opened the trade.
982
+ */
983
+ payeeSalt: Hex | null;
984
+ /** The last failure. During a trade, the phase still follows the trade. */
985
+ error: string | null;
986
+ }
987
+ interface OfframpSession {
988
+ getState(): OfframpState;
989
+ subscribe(listener: (state: OfframpState) => void): () => void;
990
+ /** Rank solvers' offers for `request`. */
991
+ quote(request: OfframpRequest): Promise<void>;
992
+ /** Seal `payee` to the chosen solver, open the trade, and lock the asset. */
993
+ confirm(payee: FiatPayee): Promise<void>;
994
+ /** The fiat arrived: release the escrow to the solver. */
995
+ confirmReceived(): Promise<void>;
996
+ /** The fiat did not arrive (or not in full): an admin decides. */
997
+ dispute(reason: string): Promise<void>;
998
+ /** Pick a trade back up by id, e.g. after a reload, and track it. */
999
+ resume(tradeId: string): Promise<void>;
1000
+ reset(): void;
1001
+ setWallet(wallet: IntentWallet | null): void;
1002
+ destroy(): void;
1003
+ }
1004
+ interface OfframpOptions extends ApiOptions {
1005
+ api?: ApiClient;
1006
+ wallet?: IntentWallet | null;
1007
+ /** Trade-status polling interval. Default 3000ms. */
1008
+ pollIntervalMs?: number;
1009
+ /** Chain reads (Permit2 allowance, receipts). Defaults to the aggregator's RPCs. */
1010
+ chainReader?: (chainId: number) => Promise<ChainReader | null>;
1011
+ }
1012
+ /**
1013
+ * Before the user signs, check that what the aggregator asks them to sign
1014
+ * is the trade they chose: this asset and amount, from this chain, to the
1015
+ * escrow, with the quoted solver as counterparty, for the quoted payout. A
1016
+ * signature is a spend authorisation, so it is never given on trust.
1017
+ */
1018
+ declare function checkLock(lock: FiatLockInstructions, trade: FiatTrade, request: OfframpRequest, quote: RankedFiatQuote, user: string): void;
1019
+ /**
1020
+ * Cash crypto out to a bank account, as a framework-free store (design §5.1):
1021
+ * quote → (approve) → open → sign the lock → the solver's relay locks it →
1022
+ * the solver pays → the user confirms or disputes.
1023
+ *
1024
+ * The bank details are sealed in the browser to the chosen solver's key;
1025
+ * the aggregator sees only the envelope and a salted commitment.
1026
+ */
1027
+ declare function createOfframpSession(options?: OfframpOptions): OfframpSession;
1028
+
1029
+ /**
1030
+ * What a user signs (EIP-191 `personal_sign`) to act on a fiat trade.
1031
+ *
1032
+ * One message per action, each naming the trade, so a signature is good for
1033
+ * exactly one action on one trade: a signature given to dispute can never be
1034
+ * replayed to confirm, which would release the escrow. These must match
1035
+ * `oif_types::fiat_trades::actions` byte for byte; both are tested against
1036
+ * `test-vectors/fiat-actions.json` at the repository root.
1037
+ *
1038
+ * The aggregator accepts only printable ASCII for reasons and references,
1039
+ * so trimming here and there cannot disagree.
1040
+ */
1041
+ /** The fiat receiver confirms the payment arrived. Releases the escrow to the payer. */
1042
+ declare function fiatConfirmMessage(tradeId: string): string;
1043
+ /** The fiat receiver contests the payer's claim. The reason is signed too. */
1044
+ declare function fiatDisputeMessage(tradeId: string, reason: string): string;
1045
+ /** A user who pays fiat (on-ramp) says it is sent, with the transfer's reference. */
1046
+ declare function fiatPaymentSentMessage(tradeId: string, reference: string): string;
1047
+
1048
+ export { type AggregationMetadata, type AmountKind, type ApiClient, ApiError, type ApiOptions, type AssetAmount, type AssetLockReference, BANK_DETAILS_HINT, CHAIN_ALIASES, type CanonicalChain, type ChainInfo, type ChainReader, DEFAULT_API_URL, DEFAULT_INTENT_MODEL, EMPTY_SUPPORTED, FIAT_CORRIDORS, FIAT_CURRENCY_ALIASES, type FiatBank, type FiatCorridor, type FiatDirection, type FiatLockInstructions, type FiatPayee, type FiatTrade, type FiatTradeState, HF_ROUTER_URL, type Input, type IntentAction, type IntentPhase, type IntentPreview, type IntentRequest, type IntentSession, type IntentState, type IntentWallet, type InteropAddress, type IssueField, type IssueKind, MAX_UINT256, NG_BANKS, type OfframpOptions, type OfframpPhase, type OfframpPreview, type OfframpRequest, type OfframpSession, type OfframpState, type OpenFiatTradeRequest, type OpenFiatTradeResponse, type OpenedPayee, type Order, type OrderPayload, type OrderRequest, type OrderResponse, type OrderStatus, type OriginSubmission, type Output, PERMIT2_ADDRESS, type ParseResult, type ParserOptions, type PayeeIdentity, type Quote, type QuotePreview, type QuoteRequest, type QuotesResponse, type RankFiatQuotesRequest, type RankFiatQuotesResponse, type RankedFiatQuote, type RawIntent, type ResolvedIntent, SOLANA_CHAIN_IDS, type SealedPayee, type SessionOptions, type Settlement, type Solver, type SolverAsset, type SolverOptions, type SolverSupportedAssets, type SolversResponse, type SupportedSet, TEMPLATE_HINT, TOKEN_ALIASES, type TypedDataSigner, type ValidationIssue, buildOrderRequest, buildPermit2ApproveRequest, buildQuoteRequest, buildSupportedSet, canonicalPayeeIdentity, chainName, checkLock, createApiClient, createIntentSession, createOfframpSession, explorerUrl, extractIntents, fiatConfirmMessage, fiatDisputeMessage, fiatPaymentSentMessage, findAsset, formatFiat, getPermit2Allowance, hasPermit2Allowance, interopAddress, isTerminalStatus, looksLikeBankDetails, normalizeChain, normalizeFiatCurrency, normalizeToken, openPayee, parseIntent, payeeCommitment, payeeIdentity, resolveIntent, resolveOfframp, rpcReaders, sealPayee, signQuote, stripDomainType, validateIntent, validateOfframp, viemWallet };