1- import { Static , Type } from "@sinclair/typebox" ;
2- import { FastifyInstance } from "fastify" ;
1+ import { Type , type Static } from "@sinclair/typebox" ;
2+ import type { FastifyInstance } from "fastify" ;
33import { StatusCodes } from "http-status-codes" ;
44import {
5- Address ,
6- estimateGasCost ,
7- prepareTransaction ,
8- sendTransaction ,
5+ toSerializableTransaction ,
6+ toTokens ,
7+ type Address ,
8+ type Hex ,
99} from "thirdweb" ;
10+ import { getChainMetadata } from "thirdweb/chains" ;
1011import { getWalletBalance } from "thirdweb/wallets" ;
1112import { getAccount } from "../../../utils/account" ;
1213import { getChain } from "../../../utils/chain" ;
14+ import { logger } from "../../../utils/logger" ;
15+ import { getChecksumAddress , maybeBigInt } from "../../../utils/primitiveTypes" ;
1316import { thirdwebClient } from "../../../utils/sdk" ;
14- import { AddressSchema } from "../../schemas/address" ;
17+ import { createCustomError } from "../../middleware/error" ;
18+ import { AddressSchema , TransactionHashSchema } from "../../schemas/address" ;
19+ import { TokenAmountStringSchema } from "../../schemas/number" ;
1520import {
1621 requestQuerystringSchema ,
1722 standardResponseSchema ,
1823} from "../../schemas/sharedApiSchemas" ;
24+ import { txOverridesSchema } from "../../schemas/txOverrides" ;
1925import {
2026 walletHeaderSchema ,
2127 walletWithAddressParamSchema ,
@@ -29,11 +35,13 @@ const requestBodySchema = Type.Object({
2935 ...AddressSchema ,
3036 description : "Address to withdraw all funds to" ,
3137 } ,
38+ ...txOverridesSchema . properties ,
3239} ) ;
3340
3441const responseBodySchema = Type . Object ( {
3542 result : Type . Object ( {
36- transactionHash : Type . String ( ) ,
43+ transactionHash : TransactionHashSchema ,
44+ amount : TokenAmountStringSchema ,
3745 } ) ,
3846} ) ;
3947
@@ -62,45 +70,69 @@ export async function withdraw(fastify: FastifyInstance) {
6270 } ,
6371 handler : async ( request , reply ) => {
6472 const { chain : chainQuery } = request . params ;
65- const { toAddress } = request . body ;
66- const {
67- "x-backend-wallet-address" : walletAddress ,
68- "x-idempotency-key" : idempotencyKey ,
69- } = request . headers as Static < typeof walletHeaderSchema > ;
73+ const { toAddress, txOverrides } = request . body ;
74+ const { "x-backend-wallet-address" : walletAddress } =
75+ request . headers as Static < typeof walletHeaderSchema > ;
7076
7177 const chainId = await getChainIdFromChain ( chainQuery ) ;
7278 const chain = await getChain ( chainId ) ;
73- const from = walletAddress as Address ;
79+ const from = getChecksumAddress ( walletAddress ) ;
80+
81+ // Populate a transfer transaction with 2x gas.
82+ const populatedTransaction = await toSerializableTransaction ( {
83+ from,
84+ transaction : {
85+ to : toAddress ,
86+ chain,
87+ client : thirdwebClient ,
88+ data : "0x" ,
89+ // Dummy value, replaced below.
90+ value : 1n ,
91+ gas : maybeBigInt ( txOverrides ?. gas ) ,
92+ maxFeePerGas : maybeBigInt ( txOverrides ?. maxFeePerGas ) ,
93+ maxPriorityFeePerGas : maybeBigInt ( txOverrides ?. maxPriorityFeePerGas ) ,
94+ } ,
95+ } ) ;
96+
97+ // Compute the maximum amount to withdraw taking into account gas fees.
98+ const value = await getWithdrawValue ( from , populatedTransaction ) ;
99+ populatedTransaction . value = value ;
74100
75101 const account = await getAccount ( { chainId, from } ) ;
76- const value = await getWithdrawValue ( { chainId, from } ) ;
102+ let transactionHash : Hex | undefined ;
103+ try {
104+ const res = await account . sendTransaction ( populatedTransaction ) ;
105+ transactionHash = res . transactionHash ;
106+ } catch ( e ) {
107+ logger ( {
108+ level : "warn" ,
109+ message : `Error withdrawing funds: ${ e } ` ,
110+ service : "server" ,
111+ } ) ;
77112
78- const transaction = prepareTransaction ( {
79- to : toAddress ,
80- chain,
81- client : thirdwebClient ,
82- value,
83- } ) ;
84- const { transactionHash } = await sendTransaction ( {
85- account,
86- transaction,
87- } ) ;
113+ const metadata = await getChainMetadata ( chain ) ;
114+ throw createCustomError (
115+ `Insufficient ${ metadata . nativeCurrency ?. symbol } on ${ metadata . name } in ${ from } . Try again when network gas fees are lower. See: https://portal.thirdweb.com/engine/troubleshooting` ,
116+ StatusCodes . BAD_REQUEST ,
117+ "INSUFFICIENT_FUNDS" ,
118+ ) ;
119+ }
88120
89121 reply . status ( StatusCodes . OK ) . send ( {
90122 result : {
91123 transactionHash,
124+ amount : toTokens ( value , 18 ) ,
92125 } ,
93126 } ) ;
94127 } ,
95128 } ) ;
96129}
97130
98- const getWithdrawValue = async ( args : {
99- chainId : number ;
100- from : Address ;
101- } ) : Promise < bigint > => {
102- const { chainId, from } = args ;
103- const chain = await getChain ( chainId ) ;
131+ const getWithdrawValue = async (
132+ from : Address ,
133+ populatedTransaction : Awaited < ReturnType < typeof toSerializableTransaction > > ,
134+ ) : Promise < bigint > => {
135+ const chain = await getChain ( populatedTransaction . chainId ) ;
104136
105137 // Get wallet balance.
106138 const { value : balanceWei } = await getWalletBalance ( {
@@ -109,17 +141,13 @@ const getWithdrawValue = async (args: {
109141 chain,
110142 } ) ;
111143
112- // Estimate gas for a transfer.
113- const transaction = prepareTransaction ( {
114- chain,
115- client : thirdwebClient ,
116- value : 1n , // dummy value
117- to : from , // dummy value
118- } ) ;
119- const { wei : transferCostWei } = await estimateGasCost ( { transaction } ) ;
120-
121- // Add a +20% buffer for gas variance.
122- const buffer = transferCostWei / 5n ;
144+ // Set the withdraw value to be the amount of gas that isn't reserved to send the transaction.
145+ const gasPrice =
146+ populatedTransaction . maxFeePerGas ?? populatedTransaction . gasPrice ;
147+ if ( ! gasPrice ) {
148+ throw new Error ( "Unable to estimate gas price for withdraw request." ) ;
149+ }
123150
124- return balanceWei - transferCostWei - buffer ;
151+ const transferCostWei = populatedTransaction . gas * gasPrice ;
152+ return balanceWei - transferCostWei ;
125153} ;
0 commit comments