From 59f5c025df9d2ca7cbd4a82012fdd525ff9dab3a Mon Sep 17 00:00:00 2001 From: Kozer4 Date: Fri, 2 Aug 2024 17:25:38 +0300 Subject: [PATCH] feat(AllbridgeCoreSdkOptions): add partnerId --- src/__tests__/index.test.ts | 10 +-- .../services/bridge/evm/index.test.ts | 6 +- .../services/bridge/trx/index.test.ts | 10 ++- src/__tests__/utils/utils.test.ts | 68 +++++++++++++++++++ src/index.ts | 7 ++ src/services/bridge/evm/index.ts | 12 +++- src/services/bridge/index.ts | 8 +-- src/services/bridge/trx/index.ts | 10 ++- src/services/index.ts | 3 +- src/utils/utils.ts | 31 +++++++++ tsconfig.json | 2 +- 11 files changed, 149 insertions(+), 18 deletions(-) create mode 100644 src/__tests__/utils/utils.test.ts diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 109596e..5620c13 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -28,6 +28,7 @@ import { import { formatAddress } from "../services/bridge/utils"; import { convertFloatAmountToInt, convertIntAmountToFloat, getFeePercent } from "../utils/calculation"; +import { addPartnerIdToValue } from "../utils/utils"; import tokensGroupedByChain from "./data/tokens-info/ChainDetailsMap.json"; import tokenInfoWithChainDetailsGrl from "./data/tokens-info/TokenInfoWithChainDetails-GRL.json"; import tokenInfoWithChainDetailsTrx from "./data/tokens-info/TokenInfoWithChainDetails-TRX.json"; @@ -716,7 +717,7 @@ describe("SDK", () => { expect(methodSendRawTransactionSpy).toHaveBeenCalledWith({ from: fromAccountAddress, to: grlChainToken.bridgeAddress, - value: fee, + value: addPartnerIdToValue(fee), data: expectedData, }); @@ -796,7 +797,7 @@ describe("SDK", () => { expect(methodTriggerSmartContractMock).toBeCalledWith( trxChainToken.bridgeAddress, "swapAndBridge(bytes32,uint256,bytes32,uint256,bytes32,uint256,uint8,uint256)", - { callValue: fee }, + { callValue: addPartnerIdToValue(fee) }, [ { type: "bytes32", @@ -892,7 +893,7 @@ describe("SDK", () => { expect(methodSendRawTransactionSpy).toHaveBeenCalledWith({ from: fromAccountAddress, to: grlChainToken.bridgeAddress, - value: "0", + value: addPartnerIdToValue("0"), data: expectedData, }); @@ -935,6 +936,7 @@ describe("SDK", () => { data: "0x331838b20000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000ddac3cb57dea3fbeff4997d78215535eb5787117000000000000000000000000c7dbc4a896b34b7a10dda2ef72052145a9122f4300000000000000000000000068d7ed9cf9881427f1db299b90fd63ef805dd10d0000000000000000000000000000000000000000000000006124fee993bc0000", from: "0x68D7ed9cf9881427F1dB299B90Fd63ef805dd10d", to: "0xba285A8F52601EabCc769706FcBDe2645aa0AF18", + value: addPartnerIdToValue("0"), }); }); test("rawTxBuilder should build swapTx correctly for trx", async () => { @@ -967,7 +969,7 @@ describe("SDK", () => { expect(methodTriggerSmartContractMock).toBeCalledWith( trxChainToken.bridgeAddress, "swap(uint256,bytes32,bytes32,address,uint256)", - { callValue: "0" }, + { callValue: addPartnerIdToValue("0") }, [ { type: "uint256", diff --git a/src/__tests__/services/bridge/evm/index.test.ts b/src/__tests__/services/bridge/evm/index.test.ts index 877bdcf..a1fe4d2 100644 --- a/src/__tests__/services/bridge/evm/index.test.ts +++ b/src/__tests__/services/bridge/evm/index.test.ts @@ -1,10 +1,12 @@ import Web3 from "web3"; import { Messenger } from "../../../../client/core-api/core-api.model"; import { AllbridgeCoreClientWithPoolInfo } from "../../../../client/core-api/core-client-base"; +import { AllbridgeCoreSdkOptions } from "../../../../index"; import { FeePaymentMethod, SendParams, TokenWithChainDetails } from "../../../../models"; import { NodeRpcUrlsConfig } from "../../../../services"; import { EvmBridgeService } from "../../../../services/bridge/evm"; import { ChainDetailsMapWithFlags } from "../../../../tokens-info"; +import { addPartnerIdToValue } from "../../../../utils/utils"; import tokensGroupedByChain from "../../../data/tokens-info/ChainDetailsMap-ETH-USDT.json"; import { mockNonce } from "../../../mock/bridge/utils"; import { initChainsWithTestnet } from "../../../mock/utils"; @@ -26,7 +28,7 @@ describe("EvmBridge", () => { }; beforeEach(() => { - evmBridge = new EvmBridgeService(new Web3(), api, new NodeRpcUrlsConfig({})); + evmBridge = new EvmBridgeService(new Web3(), api, new NodeRpcUrlsConfig({}), {} as AllbridgeCoreSdkOptions); }); describe("Given transfer params", () => { @@ -63,7 +65,7 @@ describe("EvmBridge", () => { expect(actual).toEqual({ from: from, to: bridgeAddress, - value: gasFee, + value: addPartnerIdToValue(gasFee), data: "0x4cd480bd000000000000000000000000c7dbc4a896b34b7a10dda2ef72052145a9122f4300000000000000000000000000000000000000000000000012751bf40f450000000000000000000000000000ba285a8f52601eabcc769706fcbde2645aa0af180000000000000000000000000000000000000000000000000000000000000004000000000000000000000000b10388f04f8331b59a02732cc1b6ac0d7045574b3b1200153e110000001b006132000000000000000000362600611e000000070c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000", }); }); diff --git a/src/__tests__/services/bridge/trx/index.test.ts b/src/__tests__/services/bridge/trx/index.test.ts index d13d833..116b735 100644 --- a/src/__tests__/services/bridge/trx/index.test.ts +++ b/src/__tests__/services/bridge/trx/index.test.ts @@ -3,10 +3,12 @@ import TronWeb from "tronweb"; import { ChainType } from "../../../../chains/chain.enums"; import { Messenger } from "../../../../client/core-api/core-api.model"; import { AllbridgeCoreClientWithPoolInfo } from "../../../../client/core-api/core-client-base"; +import { AllbridgeCoreSdkOptions } from "../../../../index"; import { ChainSymbol, FeePaymentMethod } from "../../../../models"; import { TxSendParams } from "../../../../services/bridge/models"; import { TronBridgeService } from "../../../../services/bridge/trx"; import { formatAddress } from "../../../../services/bridge/utils"; +import { addPartnerIdToValue } from "../../../../utils/utils"; import { mockNonce } from "../../../mock/bridge/utils"; import triggerSmartContractSendResponse from "../../../mock/tron-web/trigger-smart-contract-send.json"; @@ -23,7 +25,11 @@ describe("TrxBridge", () => { triggerSmartContract: jest.fn(), }, }; - trxBridge = new TronBridgeService(tronWebMock as typeof TronWeb, api as AllbridgeCoreClientWithPoolInfo); + trxBridge = new TronBridgeService( + tronWebMock as typeof TronWeb, + api as AllbridgeCoreClientWithPoolInfo, + {} as AllbridgeCoreSdkOptions + ); }); afterEach(() => { @@ -68,7 +74,7 @@ describe("TrxBridge", () => { expect(tronWebMock.transactionBuilder.triggerSmartContract).toHaveBeenCalledWith( bridgeAddress, "swapAndBridge(bytes32,uint256,bytes32,uint256,bytes32,uint256,uint8,uint256)", - { callValue: gasFee }, + { callValue: addPartnerIdToValue(gasFee) }, [ { type: "bytes32", diff --git a/src/__tests__/utils/utils.test.ts b/src/__tests__/utils/utils.test.ts new file mode 100644 index 0000000..614c63a --- /dev/null +++ b/src/__tests__/utils/utils.test.ts @@ -0,0 +1,68 @@ +import { InvalidAmountError } from "../../exceptions"; +import { addPartnerIdToValue, validatePartnerId } from "../../utils/utils"; + +describe("validatePartnerId", () => { + it("should not throw an error if partnerId is not provided", () => { + expect(() => validatePartnerId()).not.toThrow(); + }); + + it("should not throw an error if partnerId is within valid range", () => { + expect(() => validatePartnerId(1)).not.toThrow(); + expect(() => validatePartnerId(65535)).not.toThrow(); + expect(() => validatePartnerId(0xffff)).not.toThrow(); + }); + + it("should throw an error if partnerId is less than 0", () => { + expect(() => validatePartnerId(-1)).toThrow(InvalidAmountError); + expect(() => validatePartnerId(-1)).toThrow("Partner Id must be > 0"); + expect(() => validatePartnerId(0)).toThrow("Partner Id must be > 0"); + }); + + it("should throw an error if partnerId is greater than 65535", () => { + expect(() => validatePartnerId(65536)).toThrow(InvalidAmountError); + expect(() => validatePartnerId(65536)).toThrow("Partner Id must be < 0xffff (65535)"); + expect(() => validatePartnerId(0x10000)).toThrow("Partner Id must be < 0xffff (65535)"); + }); +}); + +describe("addPartnerIdToValue", () => { + describe("no partnerId", () => { + const testCases = [ + { input: "0", expected: "0x0" }, + { input: "100", expected: "0x0" }, + { input: "55000", expected: "0x0" }, + { input: "75000", expected: "0x10000" }, + { input: "175000", expected: "0x20000" }, + { input: "6175000", expected: "0x5e0000" }, + ]; + + test.each(testCases)("no partnerId $input", ({ input, expected }) => { + const result = addPartnerIdToValue(input); + expect(result).toEqual(expected); + }); + }); + describe("with partnerId", () => { + const testCases = [ + { input: "0", expected: "0x1", partnerId: 0x1 }, + { input: "0", expected: "0x123", partnerId: 0x123 }, + { input: "0", expected: "0x1234", partnerId: 0x1234 }, + { input: "0", expected: "0xffff", partnerId: 65535 }, + { input: "100", expected: "0x1", partnerId: 0x1 }, + { input: "55000", expected: "0x1", partnerId: 0x1 }, + { input: "55000", expected: "0x123", partnerId: 0x123 }, + { input: "55000", expected: "0x1234", partnerId: 0x1234 }, + { input: "75000", expected: "0x10123", partnerId: 0x123 }, + { input: "175000", expected: "0x21234", partnerId: 0x1234 }, + { input: "175000", expected: "0x2ffff", partnerId: 65535 }, + { input: "6175123", expected: "0x5e0001", partnerId: 0x1 }, + { input: "6175123", expected: "0x5e0123", partnerId: 0x123 }, + { input: "6175123", expected: "0x5e1234", partnerId: 0x1234 }, + { input: "6175123", expected: "0x5effff", partnerId: 65535 }, + ]; + + test.each(testCases)("partnerId $partnerId input $input", ({ input, expected, partnerId }) => { + const result = addPartnerIdToValue(input, partnerId); + expect(result).toEqual(expected); + }); + }); +}); diff --git a/src/index.ts b/src/index.ts index c320461..949f9c4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,6 +66,13 @@ export interface AllbridgeCoreSdkOptions { * Optional additional properties to merge with the default properties. */ additionalChainsProperties?: Record; + + /** + * Represents an optional identifier for a partner. + * + * @property {number} [partnerId] - The partner ID. This number should be greater than 0 and less than 0xffff (65535). + */ + partnerId?: number; } /** diff --git a/src/services/bridge/evm/index.ts b/src/services/bridge/evm/index.ts index 316f25e..013fa52 100644 --- a/src/services/bridge/evm/index.ts +++ b/src/services/bridge/evm/index.ts @@ -6,6 +6,7 @@ import Web3 from "web3"; import { TransactionConfig } from "web3-core"; import { AbiItem } from "web3-utils"; import { AllbridgeCoreClient } from "../../../client/core-api/core-client-base"; +import { AllbridgeCoreSdkOptions } from "../../../index"; import { ChainSymbol, ChainType, @@ -15,6 +16,7 @@ import { SwapParams, TransactionResponse, } from "../../../models"; +import { addPartnerIdToValue } from "../../../utils/utils"; import { NodeRpcUrlsConfig } from "../../index"; import { RawTransaction } from "../../models"; import bridgeAbi from "../../models/abi/Bridge.json"; @@ -31,7 +33,12 @@ import { formatAddress, getNonce, prepareTxSendParams, prepareTxSwapParams } fro export class EvmBridgeService extends ChainBridgeService { chainType: ChainType.EVM = ChainType.EVM; - constructor(public web3: Web3, public api: AllbridgeCoreClient, private nodeRpcUrlsConfig: NodeRpcUrlsConfig) { + constructor( + public web3: Web3, + public api: AllbridgeCoreClient, + private nodeRpcUrlsConfig: NodeRpcUrlsConfig, + private sdkOptions: AllbridgeCoreSdkOptions + ) { super(); } @@ -69,6 +76,7 @@ export class EvmBridgeService extends ChainBridgeService { return Promise.resolve({ from: fromAccountAddress, to: contractAddress, + value: addPartnerIdToValue("0", this.sdkOptions.partnerId), data: swapMethod.encodeABI(), }); } @@ -133,7 +141,7 @@ export class EvmBridgeService extends ChainBridgeService { return Promise.resolve({ from: fromAccountAddress, to: contractAddress, - value: value, + value: addPartnerIdToValue(value, this.sdkOptions.partnerId), data: sendMethod.encodeABI(), }); } diff --git a/src/services/bridge/index.ts b/src/services/bridge/index.ts index 277924e..1ee7752 100644 --- a/src/services/bridge/index.ts +++ b/src/services/bridge/index.ts @@ -150,18 +150,18 @@ export function getChainBridgeService( switch (Chains.getChainProperty(chainSymbol).chainType) { case ChainType.EVM: { if (provider) { - return new EvmBridgeService(provider as unknown as Web3, api, nodeRpcUrlsConfig); + return new EvmBridgeService(provider as unknown as Web3, api, nodeRpcUrlsConfig, params); } else { const nodeRpcUrl = nodeRpcUrlsConfig.getNodeRpcUrl(chainSymbol); - return new EvmBridgeService(new Web3(nodeRpcUrl), api, nodeRpcUrlsConfig); + return new EvmBridgeService(new Web3(nodeRpcUrl), api, nodeRpcUrlsConfig, params); } } case ChainType.TRX: { if (provider) { - return new TronBridgeService(provider, api); + return new TronBridgeService(provider, api, params); } else { const nodeRpcUrl = nodeRpcUrlsConfig.getNodeRpcUrl(chainSymbol); - return new TronBridgeService(new TronWeb({ fullHost: nodeRpcUrl }), api); + return new TronBridgeService(new TronWeb({ fullHost: nodeRpcUrl }), api, params); } } case ChainType.SOLANA: { diff --git a/src/services/bridge/trx/index.ts b/src/services/bridge/trx/index.ts index 62406f2..94c2652 100644 --- a/src/services/bridge/trx/index.ts +++ b/src/services/bridge/trx/index.ts @@ -4,7 +4,9 @@ import TronWeb from "tronweb"; import { ChainType } from "../../../chains/chain.enums"; import { AllbridgeCoreClient } from "../../../client/core-api/core-client-base"; import { SdkError } from "../../../exceptions"; +import { AllbridgeCoreSdkOptions } from "../../../index"; import { FeePaymentMethod, Messenger, SwapParams, TransactionResponse } from "../../../models"; +import { addPartnerIdToValue } from "../../../utils/utils"; import { RawTransaction, SmartContractMethodParameter } from "../../models"; import { sendRawTransaction } from "../../utils/trx"; import { SendParams, TxSendParams, TxSwapParams } from "../models"; @@ -14,7 +16,11 @@ import { getNonce, prepareTxSendParams, prepareTxSwapParams } from "../utils"; export class TronBridgeService extends ChainBridgeService { chainType: ChainType.TRX = ChainType.TRX; - constructor(public tronWeb: typeof TronWeb, public api: AllbridgeCoreClient) { + constructor( + public tronWeb: typeof TronWeb, + public api: AllbridgeCoreClient, + private sdkOptions: AllbridgeCoreSdkOptions + ) { super(); } @@ -141,7 +147,7 @@ export class TronBridgeService extends ChainBridgeService { contractAddress, methodSignature, { - callValue: value, + callValue: addPartnerIdToValue(value, this.sdkOptions.partnerId), }, parameters, fromAddress diff --git a/src/services/index.ts b/src/services/index.ts index 8549d8a..aa44d0c 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -50,7 +50,7 @@ import { swapAndBridgeFeeCalculation, swapAndBridgeFeeCalculationReverse, } from "../utils/calculation/swap-and-bridge-fee-calc"; -import { getPoolInfoByToken, validateAmountDecimals, validateAmountGtZero } from "../utils/utils"; +import { getPoolInfoByToken, validateAmountDecimals, validateAmountGtZero, validatePartnerId } from "../utils/utils"; import { BridgeService, DefaultBridgeService } from "./bridge"; import { GetNativeTokenBalanceParams } from "./bridge/models"; import { getExtraGasMaxLimits, getGasFeeOptions } from "./bridge/utils"; @@ -81,6 +81,7 @@ export class AllbridgeCoreSdkService { pool: LiquidityPoolService; constructor(nodeRpcUrlsConfig: NodeRpcUrlsConfig, params: AllbridgeCoreSdkOptions = mainnet) { + validatePartnerId(params.partnerId); Chains.addChainsProperties(params.additionalChainsProperties); const apiClient = new ApiClientImpl(params); const apiClientCaching = new ApiClientCaching(apiClient); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index e2764dd..3fe4fb1 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,8 +1,39 @@ import { Big, BigSource } from "big.js"; +import BN from "bn.js"; import { AllbridgeCoreClientPoolsExt } from "../client/core-api/core-client-pool-info-caching"; import { ArgumentInvalidDecimalsError, InvalidAmountError, TimeoutError } from "../exceptions"; import { PoolInfo, TokenWithChainDetails } from "../tokens-info"; +export function validatePartnerId(partnerId?: number): void { + if (partnerId || partnerId === 0) { + if (partnerId <= 0) { + throw new InvalidAmountError("Partner Id must be > 0"); + } + if (partnerId > 65535) { + throw new InvalidAmountError("Partner Id must be < 0xffff (65535)"); + } + } +} + +export function addPartnerIdToValue(value: string, partnerId?: number): string { + let number = new BN(value, 10); + let hexString = number.toString("hex").padStart(6, "0"); + + hexString = hexString.slice(0, -4); + + if (partnerId) { + const partnerIdHex = new BN(partnerId).toString(16).padStart(4, "0"); + hexString += partnerIdHex.slice(0, 2) + partnerIdHex.slice(2); + } else { + hexString += "0000"; + } + + hexString = hexString.replace(/^0+/, ""); + number = new BN(hexString, "hex"); + + return "0x" + number.toString("hex").padStart(hexString.length, "0"); +} + export async function getPoolInfoByToken( api: AllbridgeCoreClientPoolsExt, sourceChainToken: TokenWithChainDetails diff --git a/tsconfig.json b/tsconfig.json index a66416c..47222cf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,5 +19,5 @@ "allowJs": true, "strictNullChecks": true, "noUncheckedIndexedAccess": true - } + }, }