Skip to content

Commit 9db3923

Browse files
authored
feat: add detailed fee breakdown to offramp quotes and related logic (#625)
1 parent 6c6011e commit 9db3923

File tree

5 files changed

+90
-49
lines changed

5 files changed

+90
-49
lines changed

sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gobob/bob-sdk",
3-
"version": "3.3.2",
3+
"version": "3.3.3",
44
"main": "dist/index.js",
55
"types": "dist/index.d.ts",
66
"scripts": {

sdk/src/gateway/client.ts

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -263,17 +263,22 @@ export class GatewayApiClient {
263263
throw new Error(`Offramp API Error: ${errorMessage} ${queryParams}`);
264264
}
265265

266-
const rawQuote = await response.json();
266+
const rawQuote: OfframpQuote = await response.json();
267267
const currentUnixTimeInSec = Math.floor(Date.now() / 1000);
268268
const deadline = currentUnixTimeInSec + ORDER_DEADLINE_IN_SECONDS;
269269

270270
return {
271-
amountLockInSat: BigInt(rawQuote.amountLockInSat.toString()),
272-
feesInSat: BigInt(rawQuote.feesInSat.toString()),
273-
feeRate: BigInt(rawQuote.feeRate.toString()),
274-
deadline: BigInt(deadline.toString()),
271+
amountLockInSat: rawQuote.amountLockInSat,
275272
registryAddress: rawQuote.registryAddress as Address,
273+
deadline: deadline,
276274
token: token as Address,
275+
feeBreakdown: {
276+
overallFeeSats: rawQuote.feeBreakdown.overallFeeSats,
277+
inclusionFeeSats: rawQuote.feeBreakdown.inclusionFeeSats,
278+
protocolFeeSats: rawQuote.feeBreakdown.protocolFeeSats,
279+
affiliateFeeSats: rawQuote.feeBreakdown.affiliateFeeSats,
280+
fastestFeeRate: rawQuote.feeBreakdown.fastestFeeRate,
281+
},
277282
};
278283
}
279284

@@ -319,12 +324,13 @@ export class GatewayApiClient {
319324
return {
320325
quote: offrampQuote,
321326
offrampABI: offrampCaller,
327+
feeBreakdown: offrampQuote.feeBreakdown,
322328
offrampFunctionName: 'createOrder' as const,
323329
offrampArgs: [
324330
{
325-
satAmountToLock: offrampQuote.amountLockInSat,
326-
satFeesMax: offrampQuote.feesInSat,
327-
orderCreationDeadline: offrampQuote.deadline,
331+
satAmountToLock: BigInt(offrampQuote.amountLockInSat),
332+
satFeesMax: BigInt(offrampQuote.feeBreakdown.overallFeeSats),
333+
orderCreationDeadline: BigInt(offrampQuote.deadline),
328334
outputScript: receiverAddress as `0x${string}`,
329335
token: offrampQuote.token,
330336
orderOwner: params.fromUserAddress as Address,
@@ -369,7 +375,7 @@ export class GatewayApiClient {
369375
offrampABI: offrampCaller,
370376
offrampRegistryAddress: offrampRegistryAddress,
371377
offrampFunctionName: 'bumpFeeOfExistingOrder' as const,
372-
offrampArgs: [orderId, newFeeSat],
378+
offrampArgs: [orderId, BigInt(newFeeSat)],
373379
};
374380
}
375381

@@ -450,24 +456,22 @@ export class GatewayApiClient {
450456
token: Address,
451457
satAmountLocked: bigint,
452458
satFeesMax: bigint
453-
): Promise<[boolean, bigint, string?]> {
459+
): Promise<[boolean, number, string?]> {
454460
const decimals = getTokenDecimals(token);
455461
if (decimals === undefined) {
456-
return [false, 0n, 'Tokens with less than 8 decimals are not supported'];
462+
throw new Error('Tokens with less than 8 decimals are not supported');
457463
}
458464

459465
const amountInToken = satAmountLocked * BigInt(10 ** (decimals - 8));
460466

461-
let offrampQuote: OfframpQuote;
462467
try {
463-
offrampQuote = await this.fetchOfframpQuote(token.toString(), Number(amountInToken));
468+
const offrampQuote = await this.fetchOfframpQuote(token.toString(), Number(amountInToken));
469+
const shouldBump = satFeesMax < offrampQuote.feeBreakdown.overallFeeSats;
470+
return [shouldBump, offrampQuote.feeBreakdown.overallFeeSats];
464471
} catch (err) {
465472
// Return false and 0n with an error message if fetching the quote fails
466-
return [false, 0n, `Error fetching offramp quote: ${err.message || err}`];
473+
throw new Error(`Error fetching offramp quote: ${err.message || err}`);
467474
}
468-
469-
const shouldBump = satFeesMax < offrampQuote.feesInSat;
470-
return [shouldBump, offrampQuote.feesInSat];
471475
}
472476

473477
async canOrderBeUnlocked(
@@ -512,8 +516,8 @@ export class GatewayApiClient {
512516
return {
513517
orderId,
514518
token: order.token as Address,
515-
satAmountLocked: BigInt(order.satAmountLocked),
516-
satFeesMax: BigInt(order.satFeesMax),
519+
satAmountLocked: order.satAmountLocked,
520+
satFeesMax: order.satFeesMax,
517521
sender: order.sender as Address,
518522
receiver: order.receiver !== (ethers.ZeroAddress as Address) ? (order.receiver as Address) : null,
519523
owner: order.owner !== (ethers.ZeroAddress as Address) ? (order.owner as Address) : null,

sdk/src/gateway/types.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -355,20 +355,32 @@ export type GatewayStartOrder = GatewayCreateOrderResponse & {
355355

356356
export type OfframpOrderStatus = 'Active' | 'Accepted' | 'Processed' | 'Refunded';
357357

358+
/** @dev Detailed breakdown of fees associated with an offramp quote */
359+
export interface OfframpFeeBreakdown {
360+
/** @dev Total fees in satoshis */
361+
overallFeeSats: number;
362+
/** @dev Fee for transaction inclusion */
363+
inclusionFeeSats: number;
364+
/** @dev Protocol-specific fee */
365+
protocolFeeSats: number;
366+
/** @dev Affiliate-related fee */
367+
affiliateFeeSats: number;
368+
/** @dev Fastest available fee rate (e.g., sat/vB) */
369+
fastestFeeRate: number;
370+
}
371+
358372
/** @dev Offramp order quote returned by the quoting logic */
359373
export interface OfframpQuote {
360374
/** @dev Amount to lock in satoshis */
361-
amountLockInSat: bigint;
362-
/** @dev Maximum fee paid in satoshis */
363-
feesInSat: bigint;
375+
amountLockInSat: number;
364376
/** @dev Deadline for order creation (unix timestamp) */
365-
deadline: bigint;
377+
deadline: number;
366378
/** @dev Address of the off-ramp registry handling the order */
367379
registryAddress: Address;
368-
/** @dev Fee rate used for calculating satoshi fee */
369-
feeRate: bigint;
370380
/** @dev Token address used for payment */
371381
token: Address;
382+
/** @dev Detailed fee breakdown */
383+
feeBreakdown: OfframpFeeBreakdown;
372384
}
373385

374386
/** @dev Offramp Available Liquidity */
@@ -384,6 +396,7 @@ export interface OfframpLiquidity {
384396
/** @dev Params used for createOrder call on the off-ramp contract */
385397
export type OfframpCreateOrderParams = {
386398
quote: OfframpQuote;
399+
feeBreakdown: OfframpFeeBreakdown;
387400
offrampABI: typeof offrampCaller;
388401
offrampFunctionName: 'createOrder';
389402
offrampArgs: [

sdk/src/mempool.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ export class MempoolClient {
260260
if (!response.ok) {
261261
throw new Error(response.statusText);
262262
}
263-
return (await response.json()) as T;
263+
return response.json() as Promise<T>;
264264
}
265265

266266
/**
@@ -271,6 +271,6 @@ export class MempoolClient {
271271
if (!response.ok) {
272272
throw new Error(response.statusText);
273273
}
274-
return (await response.text()) as T;
274+
return response.text() as Promise<T>;
275275
}
276276
}

sdk/test/gateway.test.ts

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -575,12 +575,21 @@ describe('Gateway Tests', () => {
575575

576576
it('should get valid create offramp quote', async () => {
577577
const gatewaySDK = new GatewaySDK('signet');
578-
nock(`${SIGNET_GATEWAY_BASE_URL}`).get('/offramp-quote').query(true).reply(200, {
579-
amountLockInSat: 10000000000000,
580-
feesInSat: 385,
581-
registryAddress: '0xd7b27b178f6bf290155201109906ad203b6d99b1',
582-
feeRate: 1,
583-
});
578+
579+
nock(`${SIGNET_GATEWAY_BASE_URL}`)
580+
.get('/offramp-quote')
581+
.query(true)
582+
.reply(200, {
583+
amountLockInSat: 10000000000000,
584+
registryAddress: '0xd7b27b178f6bf290155201109906ad203b6d99b1',
585+
feeBreakdown: {
586+
overallFeeSats: 385,
587+
inclusionFeeSats: 384,
588+
protocolFeeSats: 1,
589+
affiliateFeeSats: 0,
590+
fastestFeeRate: 1,
591+
},
592+
});
584593

585594
const result = await gatewaySDK.createOfframpOrder({
586595
fromToken: '0xda472456b1a6a2fc9ae7edb0e007064224d4284c',
@@ -592,7 +601,7 @@ describe('Gateway Tests', () => {
592601
expect(result.offrampArgs[0]).to.deep.equal({
593602
satAmountToLock: BigInt('10000000000000'),
594603
satFeesMax: BigInt('385'),
595-
orderCreationDeadline: result.offrampArgs[0].orderCreationDeadline,
604+
orderCreationDeadline: result.offrampArgs[0].orderCreationDeadline, // timestamp is dynamic
596605
outputScript: '0x1600149d5e60f3b5cc2d246f990692ee4b267d1cd58477',
597606
token: '0xda472456b1a6a2fc9ae7edb0e007064224d4284c',
598607
orderOwner: '0xFAEe001465dE6D7E8414aCDD9eF4aC5A35B2B808',
@@ -639,7 +648,7 @@ describe('Gateway Tests', () => {
639648
});
640649
nock(SIGNET_GATEWAY_BASE_URL)
641650
.get('/offramp-registry-address')
642-
.reply(200, '"0xb74a5af78520075f90f4be803153673a162a9776"');
651+
.reply(200, '0xb74a5af78520075f90f4be803153673a162a9776');
643652

644653
const result: OfframpOrder[] = await gatewaySDK.getOfframpOrders(userAddress);
645654

@@ -663,12 +672,20 @@ describe('Gateway Tests', () => {
663672

664673
it('should return error for taproot address', async () => {
665674
const gatewaySDK = new GatewaySDK('signet');
666-
nock(`${SIGNET_GATEWAY_BASE_URL}`).get('/offramp-quote').query(true).reply(200, {
667-
amountLockInSat: 10000000000000,
668-
feesInSat: 385,
669-
registryAddress: '0xd7b27b178f6bf290155201109906ad203b6d99b1',
670-
feeRate: 1,
671-
});
675+
nock(`${SIGNET_GATEWAY_BASE_URL}`)
676+
.get('/offramp-quote')
677+
.query(true)
678+
.reply(200, {
679+
amountLockInSat: 10000000000000,
680+
registryAddress: '0xd7b27b178f6bf290155201109906ad203b6d99b1',
681+
feeBreakdown: {
682+
overallFeeSats: 385,
683+
inclusionFeeSats: 384,
684+
protocolFeeSats: 1,
685+
affiliateFeeSats: 0,
686+
fastestFeeRate: 1,
687+
},
688+
});
672689

673690
await expect(
674691
gatewaySDK.createOfframpOrder({
@@ -815,14 +832,21 @@ describe('Gateway Tests', () => {
815832
amountInWrappedToken: '1000',
816833
token: outputTokenAddress,
817834
});
818-
nock(`${SIGNET_GATEWAY_BASE_URL}`).get(`/offramp-quote?${searchParams}`).reply(201, {
819-
amountLockInSat: 10_000,
820-
feesInSat: 932,
821-
feeRate: 1000,
822-
registryAddress: zeroAddress,
823-
});
835+
nock(`${SIGNET_GATEWAY_BASE_URL}`)
836+
.get(`/offramp-quote?${searchParams}`)
837+
.reply(201, {
838+
amountLockInSat: 10_000,
839+
registryAddress: zeroAddress,
840+
feeBreakdown: {
841+
overallFeeSats: 932,
842+
inclusionFeeSats: 930,
843+
protocolFeeSats: 1,
844+
affiliateFeeSats: 1,
845+
fastestFeeRate: 1000,
846+
},
847+
});
824848

825-
nock(`${SIGNET_GATEWAY_BASE_URL}`).get(`/offramp-registry-address`).reply(201, `"${zeroAddress}"`);
849+
nock(`${SIGNET_GATEWAY_BASE_URL}`).get(`/offramp-registry-address`).reply(201, `${zeroAddress}`);
826850

827851
const evmTxId = await gatewaySDK.executeQuote(
828852
{

0 commit comments

Comments
 (0)