diff --git a/package.json b/package.json index 96fc8b3..b9a152f 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "dependencies": { "@1inch/byte-utils": "2.6.0", "@1inch/fusion-sdk": "2.1.5-rc.0", + "@openzeppelin/merkle-tree": "1.0.7", "axios": "1.7.2", "ecies-25519": "1.3.1", "ethers": "6.13.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e0bb507..30b189b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: '@1inch/fusion-sdk': specifier: 2.1.5-rc.0 version: 2.1.5-rc.0(assert@2.1.0)(axios@1.7.2) + '@openzeppelin/merkle-tree': + specifier: 1.0.7 + version: 1.0.7 assert: specifier: ^2.0.0 version: 2.1.0 @@ -549,6 +552,165 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@ethersproject/abi@5.7.0: + resolution: {integrity: sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==} + dependencies: + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/hash': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 + dev: false + + /@ethersproject/abstract-provider@5.7.0: + resolution: {integrity: sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==} + dependencies: + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/networks': 5.7.1 + '@ethersproject/properties': 5.7.0 + '@ethersproject/transactions': 5.7.0 + '@ethersproject/web': 5.7.1 + dev: false + + /@ethersproject/abstract-signer@5.7.0: + resolution: {integrity: sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==} + dependencies: + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + dev: false + + /@ethersproject/address@5.7.0: + resolution: {integrity: sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==} + dependencies: + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/rlp': 5.7.0 + dev: false + + /@ethersproject/base64@5.7.0: + resolution: {integrity: sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==} + dependencies: + '@ethersproject/bytes': 5.7.0 + dev: false + + /@ethersproject/bignumber@5.7.0: + resolution: {integrity: sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + bn.js: 5.2.1 + dev: false + + /@ethersproject/bytes@5.7.0: + resolution: {integrity: sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==} + dependencies: + '@ethersproject/logger': 5.7.0 + dev: false + + /@ethersproject/constants@5.7.0: + resolution: {integrity: sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==} + dependencies: + '@ethersproject/bignumber': 5.7.0 + dev: false + + /@ethersproject/hash@5.7.0: + resolution: {integrity: sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==} + dependencies: + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/base64': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 + dev: false + + /@ethersproject/keccak256@5.7.0: + resolution: {integrity: sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==} + dependencies: + '@ethersproject/bytes': 5.7.0 + js-sha3: 0.8.0 + dev: false + + /@ethersproject/logger@5.7.0: + resolution: {integrity: sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==} + dev: false + + /@ethersproject/networks@5.7.1: + resolution: {integrity: sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==} + dependencies: + '@ethersproject/logger': 5.7.0 + dev: false + + /@ethersproject/properties@5.7.0: + resolution: {integrity: sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==} + dependencies: + '@ethersproject/logger': 5.7.0 + dev: false + + /@ethersproject/rlp@5.7.0: + resolution: {integrity: sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + dev: false + + /@ethersproject/signing-key@5.7.0: + resolution: {integrity: sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + bn.js: 5.2.1 + elliptic: 6.5.4 + hash.js: 1.1.7 + dev: false + + /@ethersproject/strings@5.7.0: + resolution: {integrity: sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/logger': 5.7.0 + dev: false + + /@ethersproject/transactions@5.7.0: + resolution: {integrity: sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==} + dependencies: + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/rlp': 5.7.0 + '@ethersproject/signing-key': 5.7.0 + dev: false + + /@ethersproject/web@5.7.1: + resolution: {integrity: sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==} + dependencies: + '@ethersproject/base64': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 + dev: false + /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} @@ -870,6 +1032,15 @@ packages: fastq: 1.17.1 dev: true + /@openzeppelin/merkle-tree@1.0.7: + resolution: {integrity: sha512-i93t0YYv6ZxTCYU3CdO5Q+DXK0JH10A4dCBOMlzYbX+ujTXm+k1lXiEyVqmf94t3sqmv8sm/XT5zTa0+efnPgQ==} + dependencies: + '@ethersproject/abi': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + dev: false + /@pedrouid/environment@1.0.1: resolution: {integrity: sha512-HaW78NszGzRZd9SeoI3JD11JqY+lubnaOx7Pewj5pfjqWXOEATpeKIFb9Z4t2WBUK2iryiXX3lzWwmYWgUL0Ug==} dev: false @@ -1566,6 +1737,14 @@ packages: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} dev: true + /bn.js@4.12.0: + resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + dev: false + + /bn.js@5.2.1: + resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} + dev: false + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -1586,6 +1765,10 @@ packages: fill-range: 7.1.1 dev: true + /brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + dev: false + /browserslist@4.23.2: resolution: {integrity: sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -1896,6 +2079,18 @@ packages: resolution: {integrity: sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==} dev: true + /elliptic@6.5.4: + resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} + dependencies: + bn.js: 4.12.0 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + dev: false + /emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} @@ -2709,6 +2904,14 @@ packages: dependencies: function-bind: 1.1.2 + /hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + dev: false + /html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true @@ -3417,6 +3620,10 @@ packages: - ts-node dev: true + /js-sha3@0.8.0: + resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true @@ -3578,6 +3785,10 @@ packages: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} dev: false + /minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + dev: false + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: diff --git a/src/cross-chain-order/cross-chain-order.spec.ts b/src/cross-chain-order/cross-chain-order.spec.ts index 4960b6b..cd4d8c4 100644 --- a/src/cross-chain-order/cross-chain-order.spec.ts +++ b/src/cross-chain-order/cross-chain-order.spec.ts @@ -8,7 +8,8 @@ import { import {CrossChainOrder} from './cross-chain-order' import {CrossChainOrderInfo, EscrowParams} from './types' import {HashLock} from './hash-lock' -import {TimeLocks} from './time-locks/time-locks' +import {TimeLocks} from './time-locks' +import {getRandomBytes32} from '../utils/get-random-bytes-32' describe('CrossChainOrder', () => { it('Should encode/decode order', () => { @@ -26,7 +27,66 @@ describe('CrossChainOrder', () => { } const escrowParams: EscrowParams = { - hashLock: HashLock.fromSecret('123'), + hashLock: HashLock.forSingleFill(getRandomBytes32()), + srcChainId: NetworkEnum.ETHEREUM, + dstChainId: NetworkEnum.ARBITRUM, + srcSafetyDeposit: 1000n, + dstSafetyDeposit: 1000n, + timeLocks: TimeLocks.new({ + srcWithdrawal: 1n, + srcPublicWithdrawal: 2n, + srcCancellation: 3n, + srcPublicCancellation: 4n, + dstWithdrawal: 1n, + dstPublicWithdrawal: 2n, + dstCancellation: 3n + }) + } + const order = CrossChainOrder.new( + factoryAddress, + orderData, + escrowParams, + { + auction: new AuctionDetails({ + startTime: now(), + duration: 180n, + points: [], + initialRateBump: 100_000 + }), + whitelist: [{address: Address.fromBigInt(100n), allowFrom: 0n}] + }, + { + nonce: 1n + } + ) + + expect( + CrossChainOrder.fromDataAndExtension( + order.build(), + Extension.decode(order.extension.encode()) + ) + ).toEqual(order) + }) + + it('Should encode/decode order with multiple fills', () => { + const factoryAddress = Address.fromBigInt(1n) + const orderData: CrossChainOrderInfo = { + maker: Address.fromBigInt(2n), + makerAsset: new Address( + '0xdac17f958d2ee523a2206206994597c13d831ec7' + ), + takerAsset: new Address( + '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9' + ), + makingAmount: 100_000000n, + takingAmount: 90_000000n + } + + const secrets = [getRandomBytes32(), getRandomBytes32()] + const leaves = HashLock.getMerkleLeaves(secrets) + + const escrowParams: EscrowParams = { + hashLock: HashLock.forMultipleFills(leaves), srcChainId: NetworkEnum.ETHEREUM, dstChainId: NetworkEnum.ARBITRUM, srcSafetyDeposit: 1000n, @@ -86,7 +146,7 @@ describe('CrossChainOrder', () => { dstChainId: number ): CrossChainOrder => { const escrowParams: EscrowParams = { - hashLock: HashLock.fromSecret('123'), + hashLock: HashLock.forSingleFill(getRandomBytes32()), srcChainId, dstChainId, srcSafetyDeposit: 1000n, @@ -146,7 +206,7 @@ describe('CrossChainOrder', () => { } const escrowParams: EscrowParams = { - hashLock: HashLock.fromSecret('123'), + hashLock: HashLock.forSingleFill(getRandomBytes32()), srcChainId: NetworkEnum.ETHEREUM, dstChainId: NetworkEnum.ETHEREUM, srcSafetyDeposit: 1000n, diff --git a/src/cross-chain-order/escrow-extension.spec.ts b/src/cross-chain-order/escrow-extension.spec.ts index 688cca3..fdde1af 100644 --- a/src/cross-chain-order/escrow-extension.spec.ts +++ b/src/cross-chain-order/escrow-extension.spec.ts @@ -1,7 +1,8 @@ import {Address, FusionExtension, NetworkEnum} from '@1inch/fusion-sdk' import {EscrowExtension} from './escrow-extension' -import {TimeLocks} from './time-locks/time-locks' +import {TimeLocks} from './time-locks' import {HashLock} from './hash-lock' +import {getRandomBytes32} from '../utils/get-random-bytes-32' describe('EscrowExtension', () => { it('Should build/decode', () => { @@ -14,7 +15,7 @@ describe('EscrowExtension', () => { fusionExt.auctionDetails, fusionExt.postInteractionData, fusionExt.makerPermit, - HashLock.fromSecret('0xff'), + HashLock.forSingleFill(getRandomBytes32()), NetworkEnum.ARBITRUM, Address.fromBigInt(1n), 100n, diff --git a/src/cross-chain-order/escrow-extension.ts b/src/cross-chain-order/escrow-extension.ts index b45a63f..95d914a 100644 --- a/src/cross-chain-order/escrow-extension.ts +++ b/src/cross-chain-order/escrow-extension.ts @@ -11,7 +11,7 @@ import {AbiCoder} from 'ethers' import {BitMask, BN, trim0x, UINT_128_MAX} from '@1inch/byte-utils' import assert from 'assert' import {TimeLocks} from './time-locks/time-locks' -import {HashLock} from './hash-lock' +import {HashLock} from './hash-lock/hash-lock' /** * Same as FusionExtension, but with extra data at the end diff --git a/src/cross-chain-order/hash-lock.ts b/src/cross-chain-order/hash-lock.ts deleted file mode 100644 index e7af50d..0000000 --- a/src/cross-chain-order/hash-lock.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {id} from 'ethers' -import {getBytesCount} from '@1inch/byte-utils' -import assert from 'assert' - -export class HashLock { - public static Web3Type = 'bytes32' - - private readonly value: string - - protected constructor(val: string) { - this.value = val - } - - /** - * Create HashLock from keccak256 hash of secret - */ - public static fromSecret(secret: string): HashLock { - return new HashLock(id(secret)) - } - - public static fromString(hashLock: string): HashLock { - assert( - getBytesCount(hashLock) === 32n, - 'HashLock string must be result of keccak256' - ) - - return new HashLock(hashLock) - } - - public toString(): string { - return this.value - } -} diff --git a/src/cross-chain-order/hash-lock/hash-lock.spec.ts b/src/cross-chain-order/hash-lock/hash-lock.spec.ts new file mode 100644 index 0000000..de643dd --- /dev/null +++ b/src/cross-chain-order/hash-lock/hash-lock.spec.ts @@ -0,0 +1,26 @@ +import {HashLock} from './hash-lock' + +describe('HashLock', () => { + it('should create single fill HashLock', () => { + expect( + HashLock.forSingleFill( + 'S\x1D\x1D-zYO\x1C~A;\x07L{i1aHk\\I]EwH\x14J\x01y\\jE' + ).toString() + ).toEqual( + '0x9f65fdcf781d4320c2dde70da02a1fe916d595dc1817149cc4758fd6a4bfd830' + ) + }) + + it('should create multiple fill HashLock', () => { + const secrets = [ + 'S\x1D\x1D-zYO\x1C~A;\x07L{i1aHk\\I]EwH\x14J\x01y\\jE', + 'ex\x12\x13kP\x00e\x1D^\x18QmvK^f\x1Ah\x1Cv\r< + solidityPackedKeccak256( + ['uint256', 'bytes32'], + [idx, HashLock.hashSecret(s)] + ) as MerkleLeaf + ) + } + + public static fromString(value: string): HashLock { + assert( + isHexBytes(value) && getBytesCount(value) === 32n, + 'HashLock value must be bytes32 hex encoded' + ) + + return new HashLock(value) + } + + /** + * Create HashLock from keccak256 hash of secret + */ + public static forSingleFill(secret: string): HashLock { + return new HashLock(HashLock.hashSecret(secret)) + } + + public static forMultipleFills(leaves: MerkleLeaf[]): HashLock { + assert(leaves.length > 1, 'leaves array must be greater than 1') + const root = BN.fromHex(SimpleMerkleTree.of(leaves).root) + root.setMask(new BitMask(241n, 256n), BigInt(leaves.length)) + + return new HashLock(root.toHex(32)) + } + + public toString(): string { + return this.value + } +} + +type MerkleLeaf = string & {_tag: 'MerkleLeaf'} diff --git a/src/cross-chain-order/hash-lock/index.ts b/src/cross-chain-order/hash-lock/index.ts new file mode 100644 index 0000000..e4f0aa8 --- /dev/null +++ b/src/cross-chain-order/hash-lock/index.ts @@ -0,0 +1 @@ +export {HashLock} from './hash-lock' diff --git a/src/utils/get-random-bytes-32.ts b/src/utils/get-random-bytes-32.ts new file mode 100644 index 0000000..f98346b --- /dev/null +++ b/src/utils/get-random-bytes-32.ts @@ -0,0 +1,5 @@ +import {randomBytes} from 'ethers' + +export function getRandomBytes32(): string { + return [...randomBytes(32)].map((_, i) => String.fromCharCode(i)).join('') +}