Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/components/RoutesTester.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
subsidizedDelivery,
type TestMethod,
type TestMode,
type TestPriority,
type TestRoute,
} from '../lib/routes-test'
import {
Expand Down Expand Up @@ -360,6 +361,7 @@ function RouteLifecycle({
onNeedKey: () => void
}) {
const [mode, setMode] = useState<TestMode>(route.capabilities.transfer?.modes[0] ?? 'exactSource')
const [priority, setPriority] = useState<TestPriority>('price')
const [amount, setAmount] = useState('')
const [sender, setSender] = useState('')
const [recipient, setRecipient] = useState('')
Expand Down Expand Up @@ -461,7 +463,9 @@ function RouteLifecycle({
})
const tempoAccount =
tempoWallet && connection.connector?.id === tempoWallet.id ? connection.address : undefined
const subsidyAvailable = canSubsidize(route, method, mode)
// The API refuses 1:1 delivery for speed-priority transfers, so choosing speed drops it.
const speed = method === 'transfer' && priority === 'speed'
const subsidyAvailable = !speed && canSubsidize(route, method, mode)
const input = {
method,
mode,
Expand All @@ -471,6 +475,7 @@ function RouteLifecycle({
refundAddress,
// Without a key, the docs demo key quotes 1:1 delivery as a preview; it cannot create routes.
subsidize: subsidyAvailable && !subsidyRefused,
priority: speed ? priority : undefined,
}
let quoteParams: ReturnType<typeof routeQuoteRequest> | undefined
let quoteValidation = ''
Expand Down Expand Up @@ -1394,6 +1399,19 @@ function RouteLifecycle({
placeholder: walletLabel(route.sourceChain, 'source'),
})}
{recipientField(frozen, true)}
{/* Best price is the API default; speed is an opt-in shown last. */}
<label {...cx(labelStyle(), wideField())} htmlFor="routes-priority">
Priority
<Select
id="routes-priority"
disabled={frozen}
value={priority}
onChange={(e) => setPriority(e.target.value as TestPriority)}
>
<option value="price">Best price</option>
<option value="speed">Speed</option>
</Select>
</label>
</>
)}
{!isTransfer && (
Expand Down
1 change: 1 addition & 0 deletions src/lib/routes-quote-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ describe('Routes quote proxy', () => {
[{ kind: 'transfers', mode: 'exactSource', subsidize: false }, 'false'],
[{ kind: 'deposit-addresses', subsidize: false }, 'false'],
[{ kind: 'transfers', mode: 'exactDestination' }, null],
[{ kind: 'transfers', mode: 'exactSource', priority: 'speed' }, null],
])('previews 1:1 delivery by default and keeps an explicit choice: %j', async (body, subsidize) => {
expect((await POST(request(body))).status).toBe(200)
expect(new URL(String(fetchMock.mock.calls[0][0])).searchParams.get('subsidize')).toBe(
Expand Down
15 changes: 15 additions & 0 deletions src/lib/routes-test.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ describe('route test requests', () => {
).toThrow('1:1')
expect(() => routeTestRequest(route, { ...input, subsidize: true })).toThrow('1:1')
})
it('sends a speed priority on transfers only, without 1:1 delivery', () => {
expect(routeTestRequest(route, input)).not.toHaveProperty('priority')
expect(routeTestRequest(route, { ...input, priority: 'price' })).not.toHaveProperty('priority')
expect(routeQuoteRequest(route, { ...input, priority: 'speed' })).toMatchObject({
priority: 'speed',
subsidize: false,
})
const subsidized = { ...route, subsidies: { transfer: true, depositAddress: false } }
expect(() =>
routeTestRequest(subsidized, { ...input, priority: 'speed', subsidize: true }),
).toThrow('Speed')
expect(() =>
routeTestRequest(route, { ...input, method: 'depositAddress', priority: 'speed' }),
).toThrow('transfers')
})
it('requires Stellar account addresses on Stellar sources', () => {
const stellar = { id: 'stellar:pubnet', name: 'Stellar', addressFormat: 'strkey' }
const fromStellar = { ...route, sourceChain: stellar }
Expand Down
11 changes: 11 additions & 0 deletions src/lib/routes-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export const directorySchema = z.object({
export type TestRoute = z.infer<typeof routeSchema>
export type TestMethod = 'transfer' | 'depositAddress'
export type TestMode = 'exactSource' | 'exactDestination'
/** What a transfer quote optimizes for: the best guaranteed amount, or a faster route at near-best terms. */
export type TestPriority = 'price' | 'speed'

export type RoutePick = {
sourceChain: string
Expand Down Expand Up @@ -173,6 +175,7 @@ type RouteInput = {
recipient: string
refundAddress: string
subsidize?: boolean
priority?: TestPriority
}

/** Fields every quote and creation shares, after checking the route supports the request. */
Expand All @@ -181,6 +184,12 @@ function pricing(route: TestRoute, input: RouteInput) {
if (input.subsidize && !canSubsidize(route, method, mode)) {
throw new Error('This route does not offer 1:1 delivery for that funding method.')
}
if (input.priority === 'speed' && method !== 'transfer') {
throw new Error('Only transfers can prioritize speed.')
}
if (input.priority === 'speed' && input.subsidize) {
throw new Error('Speed priority cannot be combined with 1:1 delivery.')
}
if (method === 'depositAddress' && !route.capabilities.depositAddress) {
throw new Error('This route does not support deposit addresses.')
}
Expand Down Expand Up @@ -223,6 +232,8 @@ export function routeTestRequest(route: TestRoute, input: RouteInput) {
...common,
destinationChain: route.destinationChain.id,
mode: input.mode,
// Price is the API default, so only a speed choice is sent.
...(input.priority === 'speed' && { priority: input.priority }),
sender: address(input.sender, route.sourceChain.addressFormat, 'sender'),
}
: {
Expand Down
10 changes: 8 additions & 2 deletions src/pages/_api/api/routes-quote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const params = new Set([
'destinationChain',
'destinationToken',
'mode',
'priority',
'recipient',
'refundAddress',
'sender',
Expand Down Expand Up @@ -49,10 +50,15 @@ export async function POST(request: Request): Promise<Response> {
}
// The docs key's organization is approved for 1:1 delivery and cannot create routes, so its
// quotes preview subsidized terms by default. An explicit `subsidize=false` (the demo's fallback
// when a route has no subsidy) is kept; exact-destination transfers cannot be subsidized.
// when a route has no subsidy) is kept; exact-destination and speed-priority transfers cannot be
// subsidized.
if (
!target.searchParams.has('subsidize') &&
!(kind === 'transfers' && target.searchParams.get('mode') === 'exactDestination')
!(
kind === 'transfers' &&
(target.searchParams.get('mode') === 'exactDestination' ||
target.searchParams.get('priority') === 'speed')
)
)
target.searchParams.set('subsidize', 'true')
try {
Expand Down
Loading