Recursion ain't cheap
Text representations of big numbers will have lots of digits. OP has used 45 as a magic number. (Is this big enough? Who's game to start counting?)
Working with 45 suggests filling a buffer with digits for really big numbers will require in excess of 40 levels of recursion. Each function call may push onto the stack two multi-byte values (a ptr and a 16 byte int128 of some nature). This is not inconsequential.
Recommend filling the character buffer "right-to-left" simply using loop would save a LOT of processing resources. Returning a pointer to the start of those ASCII digits (perhaps &buf[10] or &buf[3]) is all that is needed. The low index elements of the character array remain unaffected until a bigger value is converted.
Further in this regard is the OP's use of the "global" int i; instead of passing another parameter down the recursion chain. This is a bad habit to get into. Better to pause and solve the problem without resorting to "quick fixes" like using global variables.
Here is a crude thing put together long ago:
char *LLtoStr( uint64_t x ) {
static char arr[ 40 + 1 ] = { 0 }; // safely ensure '\0'
for( int c = 40; x; x /= 10 )
arr[ --c ] = "0123456789"[ x % 10 ];
return arr + c;
}
The app could use %s to output the digit string, with the understanding that the function always overwrites the buffer... "use it or lose it"
(Just looking at it now, the value 0 results in "", not "0"... Something for the OP to look into.)