Gate - borrow & repay for margin
diff --git a/ts/src/base/Exchange.ts b/ts/src/base/Exchange.ts
index e014415b29b7..c8188726d539 100644
--- a/ts/src/base/Exchange.ts
+++ b/ts/src/base/Exchange.ts
@@ -4565,7 +4565,7 @@ export default class Exchange {
         }
     }
 
-    checkRequiredMarginArgument (methodName: string, symbol: string, marginMode: string) {
+    checkRequiredMarginArgument (methodName: string, symbol: Str, marginMode: string) {
         /**
          * @ignore
          * @method
diff --git a/ts/src/gate.ts b/ts/src/gate.ts
index d4020a6c710e..ca71a75c3d07 100644
--- a/ts/src/gate.ts
+++ b/ts/src/gate.ts
@@ -850,6 +850,7 @@ export default class gate extends Exchange {
                     'AUTO_TRIGGER_PRICE_LESS_LAST': InvalidOrder,  // {"label":"AUTO_TRIGGER_PRICE_LESS_LAST","message":"invalid argument: Trigger.Price must < last_price"}
                     'AUTO_TRIGGER_PRICE_GREATE_LAST': InvalidOrder, // {"label":"AUTO_TRIGGER_PRICE_GREATE_LAST","message":"invalid argument: Trigger.Price must > last_price"}
                     'POSITION_HOLDING': BadRequest,
+                    'USER_LOAN_EXCEEDED': BadRequest, // {"label":"USER_LOAN_EXCEEDED","message":"Max loan amount per user would be exceeded"}
                 },
                 'broad': {},
             },
@@ -5586,6 +5587,217 @@ export default class gate extends Exchange {
         return tiers;
     }
 
+    async repayMargin (code: string, amount, symbol: Str = undefined, params = {}) {
+        /**
+         * @method
+         * @name gate#repayMargin
+         * @description repay borrowed margin and interest
+         * @see https://www.gate.io/docs/apiv4/en/#repay-cross-margin-loan
+         * @see https://www.gate.io/docs/apiv4/en/#repay-a-loan
+         * @param {string} code unified currency code of the currency to repay
+         * @param {float} amount the amount to repay
+         * @param {string} symbol unified market symbol, required for isolated margin
+         * @param {object} [params] extra parameters specific to the exchange API endpoint
+         * @param {string} [params.mode] 'all' or 'partial' payment mode, extra parameter required for isolated margin
+         * @param {string} [params.id] '34267567' loan id, extra parameter required for isolated margin
+         * @returns {object} a [margin loan structure]{@link https://docs.ccxt.com/#/?id=margin-loan-structure}
+         */
+        let marginMode = undefined;
+        [ marginMode, params ] = this.handleOptionAndParams (params, 'repayMargin', 'marginMode');
+        this.checkRequiredArgument ('repayMargin', marginMode, 'marginMode', [ 'cross', 'isolated' ]);
+        this.checkRequiredMarginArgument ('repayMargin', symbol, marginMode);
+        await this.loadMarkets ();
+        const currency = this.currency (code);
+        const request = {
+            'currency': currency['id'].toUpperCase (),
+            'amount': this.currencyToPrecision (code, amount),
+        };
+        let response = undefined;
+        if ((marginMode === 'cross') && (symbol === undefined)) {
+            response = await this.privateMarginPostCrossRepayments (this.extend (request, params));
+        } else if ((marginMode === 'isolated') || (symbol !== undefined)) {
+            if (symbol === undefined) {
+                throw new BadRequest (this.id + ' repayMargin() requires a symbol argument for isolated margin');
+            }
+            const market = this.market (symbol);
+            request['currency_pair'] = market['id'];
+            request['type'] = 'repay';
+            response = await this.privateMarginPostUniLoans (this.extend (request, params));
+        }
+        //
+        // Cross
+        //
+        //     [
+        //         {
+        //             "id": "17",
+        //             "create_time": 1620381696159,
+        //             "update_time": 1620381696159,
+        //             "currency": "EOS",
+        //             "amount": "110.553635",
+        //             "text": "web",
+        //             "status": 2,
+        //             "repaid": "110.506649705159",
+        //             "repaid_interest": "0.046985294841",
+        //             "unpaid_interest": "0.0000074393366667"
+        //         }
+        //     ]
+        //
+        // Isolated
+        //
+        //     {
+        //         "id": "34267567",
+        //         "create_time": "1656394778",
+        //         "expire_time": "1657258778",
+        //         "status": "finished",
+        //         "side": "borrow",
+        //         "currency": "USDT",
+        //         "rate": "0.0002",
+        //         "amount": "100",
+        //         "days": 10,
+        //         "auto_renew": false,
+        //         "currency_pair": "LTC_USDT",
+        //         "left": "0",
+        //         "repaid": "100",
+        //         "paid_interest": "0.003333333333",
+        //         "unpaid_interest": "0"
+        //     }
+        //
+        if (marginMode === 'cross') {
+            response = response[0];
+        }
+        return this.parseMarginLoan (response, currency);
+    }
+
+    async borrowMargin (code: string, amount, symbol: Str = undefined, params = {}) {
+        /**
+         * @method
+         * @name gate#borrowMargin
+         * @description create a loan to borrow margin
+         * @see https://www.gate.io/docs/apiv4/en/#create-a-cross-margin-borrow-loan
+         * @see https://www.gate.io/docs/developers/apiv4/en/#marginuni
+         * @param {string} code unified currency code of the currency to borrow
+         * @param {float} amount the amount to borrow
+         * @param {string} symbol unified market symbol, required for isolated margin
+         * @param {object} [params] extra parameters specific to the exchange API endpoint
+         * @param {string} [params.rate] '0.0002' or '0.002' extra parameter required for isolated margin
+         * @returns {object} a [margin loan structure]{@link https://docs.ccxt.com/#/?id=margin-loan-structure}
+         */
+        let marginMode = undefined;
+        [ marginMode, params ] = this.handleOptionAndParams (params, 'borrowMargin', 'marginMode');
+        this.checkRequiredArgument ('borrowMargin', marginMode, 'marginMode', [ 'cross', 'isolated' ]);
+        this.checkRequiredMarginArgument ('borrowMargin', symbol, marginMode);
+        await this.loadMarkets ();
+        const currency = this.currency (code);
+        const request = {
+            'currency': currency['id'].toUpperCase (),
+            'amount': this.currencyToPrecision (code, amount),
+        };
+        let response = undefined;
+        if ((marginMode === 'cross') && (symbol === undefined)) {
+            response = await this.privateMarginPostCrossLoans (this.extend (request, params));
+        } else if ((marginMode === 'isolated') || (symbol !== undefined)) {
+            if (symbol === undefined) {
+                throw new BadRequest (this.id + ' borrowMargin() requires a symbol argument for isolated margin');
+            }
+            const market = this.market (symbol);
+            request['currency_pair'] = market['id'];
+            request['type'] = 'borrow';
+            response = await this.privateMarginPostUniLoans (this.extend (request, params));
+        }
+        //
+        // Cross
+        //
+        //     {
+        //         "id": "17",
+        //         "create_time": 1620381696159,
+        //         "update_time": 1620381696159,
+        //         "currency": "EOS",
+        //         "amount": "110.553635",
+        //         "text": "web",
+        //         "status": 2,
+        //         "repaid": "110.506649705159",
+        //         "repaid_interest": "0.046985294841",
+        //         "unpaid_interest": "0.0000074393366667"
+        //     }
+        //
+        // Isolated
+        //
+        //     {
+        //         "id": "34267567",
+        //         "create_time": "1656394778",
+        //         "expire_time": "1657258778",
+        //         "status": "loaned",
+        //         "side": "borrow",
+        //         "currency": "USDT",
+        //         "rate": "0.0002",
+        //         "amount": "100",
+        //         "days": 10,
+        //         "auto_renew": false,
+        //         "currency_pair": "LTC_USDT",
+        //         "left": "0",
+        //         "repaid": "0",
+        //         "paid_interest": "0",
+        //         "unpaid_interest": "0.003333333333"
+        //     }
+        //
+        return this.parseMarginLoan (response, currency);
+    }
+
+    parseMarginLoan (info, currency: Currency = undefined) {
+        //
+        // Cross
+        //
+        //     {
+        //         "id": "17",
+        //         "create_time": 1620381696159,
+        //         "update_time": 1620381696159,
+        //         "currency": "EOS",
+        //         "amount": "110.553635",
+        //         "text": "web",
+        //         "status": 2,
+        //         "repaid": "110.506649705159",
+        //         "repaid_interest": "0.046985294841",
+        //         "unpaid_interest": "0.0000074393366667"
+        //     }
+        //
+        // Isolated
+        //
+        //     {
+        //         "id": "34267567",
+        //         "create_time": "1656394778",
+        //         "expire_time": "1657258778",
+        //         "status": "loaned",
+        //         "side": "borrow",
+        //         "currency": "USDT",
+        //         "rate": "0.0002",
+        //         "amount": "100",
+        //         "days": 10,
+        //         "auto_renew": false,
+        //         "currency_pair": "LTC_USDT",
+        //         "left": "0",
+        //         "repaid": "0",
+        //         "paid_interest": "0",
+        //         "unpaid_interest": "0.003333333333"
+        //     }
+        //
+        const marginMode = this.safeString2 (this.options, 'defaultMarginMode', 'marginMode', 'cross');
+        let timestamp = this.safeInteger (info, 'create_time');
+        if (marginMode === 'isolated') {
+            timestamp = this.safeTimestamp (info, 'create_time');
+        }
+        const currencyId = this.safeString (info, 'currency');
+        const marketId = this.safeString (info, 'currency_pair');
+        return {
+            'id': this.safeInteger (info, 'id'),
+            'currency': this.safeCurrencyCode (currencyId, currency),
+            'amount': this.safeNumber (info, 'amount'),
+            'symbol': this.safeSymbol (marketId, undefined, '_', 'margin'),
+            'timestamp': timestamp,
+            'datetime': this.iso8601 (timestamp),
+            'info': info,
+        };
+    }
+
     sign (path, api = [], method = 'GET', params = {}, headers = undefined, body = undefined) {
         const authentication = api[0]; // public, private
         const type = api[1]; // spot, margin, future, delivery
diff --git a/ts/src/test/static/request/gate.json b/ts/src/test/static/request/gate.json
index 3c5e419db3ca..5839e377dfad 100644
--- a/ts/src/test/static/request/gate.json
+++ b/ts/src/test/static/request/gate.json
@@ -239,6 +239,58 @@
                 ],
                 "output": "{\"currency\":\"usdt\",\"amount\":\"1\",\"from\":\"futures\",\"to\":\"spot\",\"settle\":\"usdt\"}"
             }
+        ],
+        "borrowMargin": [
+            {
+                "description": "borrow cross margin",
+                "method": "borrowMargin",
+                "url": "https://api.gateio.ws/api/v4/margin/cross/loans",
+                "input": [
+                  "USDT",
+                  "1",
+                  null,
+                  {"marginMode":"cross"}
+                ],
+                "output": "{\"currency\":\"USDT\",\"amount\":\"1\"}"
+            },
+            {
+                "description": "borrow isolated margin",
+                "method": "borrowMargin",
+                "url": "https://api.gateio.ws/api/v4/margin/uni/loans",
+                "input": [
+                  "USDT",
+                  "1",
+                  "LTC/USDT",
+                  {"marginMode":"isolated"}
+                ],
+                "output": "{\"currency\":\"USDT\",\"amount\":\"1\",\"currency_pair\":\"LTC_USDT\",\"type\":\"borrow\"}"
+            }
+        ],
+        "repayMargin" : [
+            {
+                "description": "repay cross margin",
+                "method": "repayMargin",
+                "url": "https://api.gateio.ws/api/v4/margin/cross/repayments",
+                "input": [
+                  "USDT",
+                  "1",
+                  null,
+                  {"marginMode":"cross"}
+                ],
+                "output": "{\"currency\":\"USDT\",\"amount\":\"1\"}"
+            },
+            {
+                "description": "repay isolated margin",
+                "method": "repayMargin",
+                "url": "https://api.gateio.ws/api/v4/margin/uni/loans",
+                "input": [
+                  "USDT",
+                  "1",
+                  "LTC/USDT",
+                  {"marginMode":"isolated"}
+                ],
+                "output": "{\"currency\":\"USDT\",\"amount\":\"1\",\"currency_pair\":\"LTC_USDT\",\"type\":\"repay\"}"
+            }
         ]
     }
 }

await e.borrowMargin ('USDT', 1, undefined, {marginMode: 'cross'});
```
{
  id: 17835350,
  currency: "USDT",
  amount: 4,
  symbol: undefined,
  timestamp: 1700893271174,
  datetime: "2023-11-25T06:21:11.174Z",
  info: {
    id: "17835350",
    create_time: "1700893271174",
    update_time: "1700898941442",
    currency: "USDT",
    amount: "4",
    text: "apiv4",
    status: "2",
    repaid: "0",
    repaid_interest: "0",
    unpaid_interest: "0",
  },
}
```


await e.repayMargin ('USDT', 2, undefined, {marginMode: 'cross'});
```
{
  id: 22712625,
  currency: "USDT",
  amount: 1,
  symbol: undefined,
  timestamp: 1700893271174,
  datetime: "2023-11-25T06:21:11.174Z",
  info: {
    id: "22712625",
    create_time: "1700893271174",
    update_time: "1700898887015",
    currency: "USDT",
    amount: "1",
    text: "apiv4",
    status: "3",
    repaid: "2",
    repaid_interest: "0",
    unpaid_interest: "0",
  },
}
```