Repository: ccxt/ccxt
Pull request: feat(coinbase): add pagination in fetchLedger
fix: ccxt/ccxt#21756

Test:
```BASH
# should fetch all history
$  n coinbase fetchLedger USDT 1673986028000 100 '{"paginate": true,"account_id":"0e2b7cd9-ce67-53f3-92f2-d61c9410a9f6"}'
$  n coinbase fetchLedger USDT 1673986028000 1 '{"paginate": true,"account_id":"0e2b7cd9-ce67-53f3-92f2-d61c9410a9f6"}'

# should fetch single history
$  n coinbase fetchLedger USDT 1673986028000 1 '{"account_id":"0e2b7cd9-ce67-53f3-92f2-d61c9410a9f6"}'
```

Problem statement:
Coinbase fetch_ledger unable to increase limit or paginate results - stuck receiving 25 results?
### Operating System

ubuntu and mac

### Programming Languages

Python

### CCXT Version

4.2.65

### Description

Can't figure out how to enable pagination using ccxt, coinbase.fetch_ledger() method.  Anyone have example?

If I set 
params = { 'paginate': True } 
I get an Authentication error - the account_id is not getting set in the v2 URL ... 

### Code

```
  params = { }
  ledger = exchange.fetch_ledger(code=code, since=since, limit=limit, params=params)

```

Hints from issue discussion:
@jtashiro before we wrap pagination in this function, you can set `'starting_after':'value_next_after'` manually. The value would be `"pagination.next_starting_after"`
Can you provide an example?  I'm not clear where and how I set those properties.  
Actually am seeing this problem across a number of the v2  APIs where pagination is failing when submitted as a parameter.  These APIs are limited to 25 results and didnt realize that until I started having more than 25 deposits/withdrawals.

ERROR: coinbase {"errors":[{"id":"authentication_error","message":"invalid signature"}]}

@jtashiro thanks, I think we missed pagination in this function. I'll add asap. For the signature issue, we've fixed that, could you upgrade and try again?

Commit message: feat(coinbase): add pagination in fetchLedger

Patch to review:
diff --git a/ts/src/coinbase.ts b/ts/src/coinbase.ts
index 989c99836d456..3c05cea3a9679 100644
--- a/ts/src/coinbase.ts
+++ b/ts/src/coinbase.ts
@@ -1897,9 +1897,15 @@ export default class coinbase extends Exchange {
          * @param {int} [since] timestamp in ms of the earliest ledger entry, default is undefined
          * @param {int} [limit] max number of ledger entrys to return, default is undefined
          * @param {object} [params] extra parameters specific to the exchange API endpoint
+         * @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
          * @returns {object} a [ledger structure]{@link https://docs.ccxt.com/#/?id=ledger-structure}
          */
         await this.loadMarkets ();
+        let paginate = false;
+        [ paginate, params ] = this.handleOptionAndParams (params, 'fetchLedger', 'paginate');
+        if (paginate) {
+            return await this.fetchPaginatedCallCursor ('fetchLedger', undefined, undefined, undefined, params, 'next_starting_after', 'starting_after', undefined, 100);
+        }
         let currency = undefined;
         if (code !== undefined) {
             currency = this.currency (code);
@@ -1910,7 +1916,17 @@ export default class coinbase extends Exchange {
         // the value for the next page can be obtained from the result of the previous call in the 'pagination' field
         // eg: instance.last_json_response.pagination.next_starting_after
         const response = await this.v2PrivateGetAccountsAccountIdTransactions (this.extend (request, params));
-        return this.parseLedger (response['data'], currency, since, limit);
+        const ledger = this.parseLedger (response['data'], currency, since, limit);
+        const length = ledger.length;
+        const lastIndex = length - 1;
+        const last = this.safeDict (ledger, lastIndex);
+        const pagination = this.safeDict (response, 'pagination', {});
+        const cursor = this.safeString (pagination, 'next_starting_after');
+        if ((cursor !== undefined) && (cursor !== '')) {
+            last['next_starting_after'] = cursor;
+            ledger[lastIndex] = last;
+        }
+        return ledger;
     }
 
     parseLedgerEntryStatus (status) {


Code review comments:
diff_hunk: @@ -1897,9 +1897,15 @@ export default class coinbase extends Exchange {
          * @param {int} [since] timestamp in ms of the earliest ledger entry, default is undefined
          * @param {int} [limit] max number of ledger entrys to return, default is undefined
          * @param {object} [params] extra parameters specific to the exchange API endpoint
+         * @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
          * @returns {object} a [ledger structure]{@link https://docs.ccxt.com/#/?id=ledger-structure}
          */
         await this.loadMarkets ();
+        let paginate = false;
+        [ paginate, params ] = this.handleOptionAndParams (params, 'fetchLedger', 'paginate');
+        if (paginate) {
+            return await this.fetchPaginatedCallCursor ('fetchLedger', undefined, undefined, undefined, params, 'next_starting_after', 'starting_after', undefined, 100);
path: ts/src/coinbase.ts
text: @user1:
I think we should add ` code, since, limit` here