diff --git a/.env.example b/.env.example index 77eb3d9..402fdac 100644 --- a/.env.example +++ b/.env.example @@ -182,4 +182,43 @@ TON_TOKEN_AMOUNT=10000 TON_TRANSFER_TX='6f97ca02d8f20151210ca2bef32340804214e4f74eebf6a9edf13b727ac2527e' TON_TOKEN_TRANSFER_TX='e007ce43b116fd283364527c29411eb0cece2a49df776bf1990f2117747f3e2c' TON_NFT_TRANSFER_TX='75f3029eaa33a56673fb4a3a449d972dd6af16e6f2f91ca9273c76bf9ad860f4' -#TON \ No newline at end of file +#TON + +#SUI +# Assets +SUI_COIN_TRANSFER_TEST_IS_ACTIVE=false +SUI_TOKEN_TRANSFER_TEST_IS_ACTIVE=false +SUI_TOKEN_APPROVE_TEST_IS_ACTIVE=false +SUI_TOKEN_TRANSFER_FROM_TEST_IS_ACTIVE=false +SUI_NFT_TRANSACTION_TEST_IS_ACTIVE=false +SUI_TRANSACTION_LISTENER_TEST_IS_ACTIVE=false + +SUI_COIN_BALANCE_TEST_AMOUNT=100 +SUI_TOKEN_BALANCE_TEST_AMOUNT=10 +SUI_NFT_BALANCE_TEST_AMOUNT=1 +SUI_TRANSFER_TEST_AMOUNT=0.001 +SUI_TOKEN_TRANSFER_TEST_AMOUNT=1 +SUI_TOKEN_APPROVE_TEST_AMOUNT=10 + +SUI_BALANCE_TEST_ADDRESS='0xd4a5e15e39bed8eb14a87459e2cb43fcec3c0653002e5a9c31320ba8964b6052' +SUI_SENDER_PRIVATE_KEY='suiprivkey1qrcamlu07sa6jwv9j8f7ranaq20qgak8tphs6lycpr02qtuuvgsty2qfauw' +SUI_RECEIVER_PRIVATE_KEY='suiprivkey1qqek0d9vkssedsyh5uyjug3dpyplcur7fhgqgwmxemypm8nvlpr9xtpym5z' +SUI_SENDER_TEST_ADDRESS='0xd68cb1e0d64372021cd6fd54940d213c939d16cd4667bba507df880f1e17c78b' +SUI_RECEIVER_TEST_ADDRESS='0xda4558a29f4c2dd54d3fbcaf66b22eea73772dd893ebfccc973609d3457cddfd' +SUI_TOKEN_TYPE_ADDRESS='0xdb2062063e6756bb0c39c1c4a208a8b341f2241d941621ee5c52f00b13e4cb46::Test_USDC::TEST_USDC' +SUI_NFT_TYPE_ADDRESS='0xd324a3ddcd34338b978a02b17407781bfc17cb0b432c38c2e60033522a5e4045::Test_NFT::TEST_NFT' +SUI_NFT_OBJECT_ID='0x57f764ca497379aca2553ceaccd319194d8057999554a0d0c0e99805f1d0eb9d' +# Assets + +# Models +SUI_MODEL_NFT_OBJECT_ID='0x2bf1cca46dd55dcc2daa021cd6c0adf1cf3b705b0ec20158429d672db77a00ee' +SUI_MODEL_TOKEN_AMOUNT=10 +SUI_MODEL_COIN_AMOUNT=50 + +SUI_MODEL_TEST_SENDER='0xd68cb1e0d64372021cd6fd54940d213c939d16cd4667bba507df880f1e17c78b' +SUI_MODEL_TEST_RECEIVER='0xd4a5e15e39bed8eb14a87459e2cb43fcec3c0653002e5a9c31320ba8964b6052' + +SUI_TRANSFER_TX='22exMwAKinLgGjd9RqawzYmFb7XUhLnvGWXsLFXgBYRH' +SUI_TOKEN_TRANSFER_TX='3EnacLHhd3Qr3gTv3wENH1etrKLvJUSjxYqPniB1ENnA' +SUI_NFT_TRANSFER_TX='AFHLJoEsLjGfBf668SGsAUtiHXtrFywG2xWHareVBbWe' +#SUI diff --git a/packages/networks/bitcoin/package.json b/packages/networks/bitcoin/package.json index 669d8ad..3105e7e 100644 --- a/packages/networks/bitcoin/package.json +++ b/packages/networks/bitcoin/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/bitcoin", - "version": "0.4.18", + "version": "0.4.20", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", diff --git a/packages/networks/boilerplate/package.json b/packages/networks/boilerplate/package.json index fa2557f..3381c9a 100644 --- a/packages/networks/boilerplate/package.json +++ b/packages/networks/boilerplate/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/boilerplate", - "version": "0.1.0", + "version": "0.4.20", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", @@ -10,21 +10,21 @@ "exports": { ".": { "import": { - "default": "./dist/index.es.js", - "types": "./dist/browser/index.d.ts" + "types": "./dist/browser/index.d.ts", + "default": "./dist/index.es.js" }, "require": { - "default": "./dist/index.cjs", - "types": "./dist/index.d.ts" + "types": "./dist/index.d.ts", + "default": "./dist/index.cjs" } }, "./node": { - "default": "./dist/index.cjs", - "types": "./dist/index.d.ts" + "types": "./dist/index.d.ts", + "default": "./dist/index.cjs" }, "./browser": { - "default": "./dist/index.es.js", - "types": "./dist/browser/index.d.ts" + "types": "./dist/browser/index.d.ts", + "default": "./dist/index.es.js" } }, "typesVersions": { diff --git a/packages/networks/evm-chains/package.json b/packages/networks/evm-chains/package.json index f07e6cd..1276863 100644 --- a/packages/networks/evm-chains/package.json +++ b/packages/networks/evm-chains/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/evm-chains", - "version": "0.4.18", + "version": "0.4.20", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", @@ -82,7 +82,6 @@ "@web3modal/core": "4.1.11", "@web3modal/ethers": "4.1.11", "@web3modal/scaffold-utils": "4.1.11", - "axios": "^1.6.8", "ethers": "^6.13.4", "viem": "^2.21.43" } diff --git a/packages/networks/evm-chains/pnpm-lock.yaml b/packages/networks/evm-chains/pnpm-lock.yaml index 43e24f6..bb2ba9d 100644 --- a/packages/networks/evm-chains/pnpm-lock.yaml +++ b/packages/networks/evm-chains/pnpm-lock.yaml @@ -29,9 +29,6 @@ importers: '@web3modal/scaffold-utils': specifier: 4.1.11 version: 4.1.11(react@18.2.0) - axios: - specifier: ^1.6.8 - version: 1.6.8 ethers: specifier: ^6.13.4 version: 6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -587,9 +584,6 @@ packages: async-mutex@0.2.6: resolution: {integrity: sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw==} - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} @@ -598,9 +592,6 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axios@1.6.8: - resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==} - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -664,10 +655,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - confbox@0.1.7: resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} @@ -725,10 +712,6 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - destr@2.0.3: resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} @@ -820,22 +803,9 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - follow-redirects@1.15.6: - resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1060,14 +1030,6 @@ packages: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -1231,9 +1193,6 @@ packages: proxy-compare@2.5.1: resolution: {integrity: sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - qrcode@1.5.3: resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==} engines: {node: '>=10.13.0'} @@ -2741,22 +2700,12 @@ snapshots: dependencies: tslib: 2.7.0 - asynckit@0.4.0: {} - atomic-sleep@1.0.0: {} available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 - axios@1.6.8: - dependencies: - follow-redirects: 1.15.6 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - base64-js@1.5.1: {} bignumber.js@9.1.2: {} @@ -2829,10 +2778,6 @@ snapshots: color-name@1.1.4: {} - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - confbox@0.1.7: {} consola@3.2.3: {} @@ -2873,8 +2818,6 @@ snapshots: defu@6.1.4: {} - delayed-stream@1.0.0: {} - destr@2.0.3: {} detect-browser@5.3.0: {} @@ -3002,18 +2945,10 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 - follow-redirects@1.15.6: {} - for-each@0.3.3: dependencies: is-callable: 1.2.7 - form-data@4.0.0: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - fsevents@2.3.3: optional: true @@ -3254,12 +3189,6 @@ snapshots: braces: 3.0.2 picomatch: 2.3.1 - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - mime@3.0.0: {} mimic-fn@4.0.0: {} @@ -3403,8 +3332,6 @@ snapshots: proxy-compare@2.5.1: {} - proxy-from-env@1.1.0: {} - qrcode@1.5.3: dependencies: dijkstrajs: 1.0.3 diff --git a/packages/networks/evm-chains/src/browser/Wallet.ts b/packages/networks/evm-chains/src/browser/Wallet.ts index 76245fc..e6e5876 100644 --- a/packages/networks/evm-chains/src/browser/Wallet.ts +++ b/packages/networks/evm-chains/src/browser/Wallet.ts @@ -19,8 +19,8 @@ const rejectMap = (error: any, reject: (a: any) => any): any => { const errorMessage = String(error.message ?? '') if ( - errorMessage === 'Not supported chainId' || errorMessage.includes('chain ID') || + errorMessage.includes('Not supported chainId') || errorMessage.includes('networkConfigurationId') || errorMessage.includes('The Provider is not connected to the requested chain.') ) { diff --git a/packages/networks/evm-chains/src/browser/adapters/switcher.ts b/packages/networks/evm-chains/src/browser/adapters/switcher.ts index d104a8a..bd8d47c 100644 --- a/packages/networks/evm-chains/src/browser/adapters/switcher.ts +++ b/packages/networks/evm-chains/src/browser/adapters/switcher.ts @@ -78,11 +78,9 @@ export const switcher = async (wallet: EIP1193Provider, provider?: Provider): Pr reject(error) }) } else { - if ( - (typeof error === 'object' ? error : {}).message.includes( - 'wallet_switchEthereumChain' - ) === true - ) { + const message = + typeof error === 'object' ? String(error?.message ?? '') : '' + if (message.includes('wallet_switchEthereumChain')) { return } reject(error) diff --git a/packages/networks/evm-chains/src/services/Provider.ts b/packages/networks/evm-chains/src/services/Provider.ts index 36016ef..7be6e43 100644 --- a/packages/networks/evm-chains/src/services/Provider.ts +++ b/packages/networks/evm-chains/src/services/Provider.ts @@ -1,4 +1,3 @@ -import axios from 'axios' import { Ethers } from './Ethers' import { ErrorTypeEnum, @@ -7,6 +6,7 @@ import { } from '@multiplechain/types' import { checkWebSocket } from '@multiplechain/utils' +import { JsonRpcProvider } from 'ethers' export interface EvmNetworkConfigInterface extends NetworkConfigInterface { id: number @@ -74,17 +74,8 @@ export class Provider implements ProviderInterface { */ async checkRpcConnection(url?: string): Promise { try { - const response = await axios.post(url ?? this.network.rpcUrl, { - jsonrpc: '2.0', - method: 'eth_blockNumber', - params: [], - id: 1 - }) - - if (response.status !== 200) { - return new Error(response.statusText + ': ' + JSON.stringify(response.data)) - } - + const rpc = new JsonRpcProvider(url ?? this.network.rpcUrl ?? '') + await rpc.getBlockNumber() return true } catch (error) { return error as any diff --git a/packages/networks/solana/package.json b/packages/networks/solana/package.json index 546b907..cf6d30e 100644 --- a/packages/networks/solana/package.json +++ b/packages/networks/solana/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/solana", - "version": "0.4.18", + "version": "0.4.20", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", @@ -87,7 +87,6 @@ "@solana/wallet-adapter-trust": "^0.1.13", "@solana/wallet-adapter-walletconnect": "^0.1.16", "@solana/web3.js": "^1.91.8", - "@walletconnect/types": "^2.17.2", - "axios": "^1.6.8" + "@walletconnect/types": "^2.17.2" } } diff --git a/packages/networks/solana/pnpm-lock.yaml b/packages/networks/solana/pnpm-lock.yaml index c10c31e..8c1ac3a 100644 --- a/packages/networks/solana/pnpm-lock.yaml +++ b/packages/networks/solana/pnpm-lock.yaml @@ -56,9 +56,6 @@ importers: '@walletconnect/types': specifier: ^2.17.2 version: 2.17.2 - axios: - specifier: ^1.6.8 - version: 1.6.8(debug@4.3.4) packages: diff --git a/packages/networks/solana/src/services/Provider.ts b/packages/networks/solana/src/services/Provider.ts index f366a15..7565334 100644 --- a/packages/networks/solana/src/services/Provider.ts +++ b/packages/networks/solana/src/services/Provider.ts @@ -1,4 +1,3 @@ -import axios from 'axios' import { ErrorTypeEnum, type NetworkConfigInterface, @@ -92,16 +91,8 @@ export class Provider implements ProviderInterface { */ async checkRpcConnection(url?: string): Promise { try { - const response = await axios.post(url ?? this.node.rpcUrl, { - jsonrpc: '2.0', - id: 1, - method: 'getEpochInfo' - }) - - if (response.status !== 200) { - return new Error(response.statusText + ': ' + JSON.stringify(response.data)) - } - + const conn = new Connection(url ?? this.node.rpcUrl ?? '') + await conn.getEpochInfo() return true } catch (error) { return error as any diff --git a/packages/networks/solana/src/services/TransactionSigner.ts b/packages/networks/solana/src/services/TransactionSigner.ts index 95c7b2b..e135a62 100644 --- a/packages/networks/solana/src/services/TransactionSigner.ts +++ b/packages/networks/solana/src/services/TransactionSigner.ts @@ -71,6 +71,7 @@ export class TransactionSigner recoveredTransaction = RawTransaction.from(Buffer.from(encodedTransaction, 'base64')) } catch (error) { recoveredTransaction = VersionedTransaction.deserialize( + // @ts-expect-error ignore Buffer.from(encodedTransaction, 'base64') ) } diff --git a/packages/networks/solana/tests/assets.spec.ts b/packages/networks/solana/tests/assets.spec.ts index e795d66..4c4efb8 100644 --- a/packages/networks/solana/tests/assets.spec.ts +++ b/packages/networks/solana/tests/assets.spec.ts @@ -211,6 +211,8 @@ describe('Token', async () => { }) it('Transfer from', async () => { + await waitSecondsBeforeThanNewTx(5) + const signer = await token.transferFrom( receiverTestAddress, senderTestAddress, diff --git a/packages/networks/sui/.eslintrc.json b/packages/networks/sui/.eslintrc.json new file mode 100644 index 0000000..9e99876 --- /dev/null +++ b/packages/networks/sui/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["../../../.eslintrc.json"] +} diff --git a/packages/networks/sui/README.md b/packages/networks/sui/README.md new file mode 100644 index 0000000..3254181 --- /dev/null +++ b/packages/networks/sui/README.md @@ -0,0 +1,5 @@ +# MultipleChain Sui + +MultipleChain aims for easy access by simplifying the complex structure of many blockchains. For example, each blockchain network has a different structure for transfer initiation or transaction data. You may need to learn new things from scratch for each blockchain network. This is necessary if you want to go into detail. But if you just want to get to the basics. MultipleChain will make your work much easier. In many different programming languages. + +#### 📚 [Documentation](https://multiplechain.gitbook.io/multiplechain-docs) diff --git a/packages/networks/sui/esbuild.ts b/packages/networks/sui/esbuild.ts new file mode 100644 index 0000000..f25aad1 --- /dev/null +++ b/packages/networks/sui/esbuild.ts @@ -0,0 +1,3 @@ +void import('../../../esbuild').then((module) => { + module.default() +}) diff --git a/packages/networks/sui/index.example.html b/packages/networks/sui/index.example.html new file mode 100644 index 0000000..912766e --- /dev/null +++ b/packages/networks/sui/index.example.html @@ -0,0 +1,320 @@ + + + + + + + Browser Tests + + + + +
+
    +
    + +
    +
    Adapter id:
    +
    Adapter name:
    +
    + Adapter icon: + icon +
    +
    Platforms:
    +
    Download link:
    +
    Deep link:
    +
    + Connected address: +
    + +
    Result:
    + +
    Result:
    + +
    Result:
    +
    + + + + diff --git a/packages/networks/sui/package.json b/packages/networks/sui/package.json new file mode 100644 index 0000000..46f0488 --- /dev/null +++ b/packages/networks/sui/package.json @@ -0,0 +1,82 @@ +{ + "name": "@multiplechain/sui", + "version": "0.4.20", + "type": "module", + "main": "dist/index.cjs", + "module": "dist/index.es.js", + "unpkg": "dist/index.umd.js", + "browser": "dist/index.umd.js", + "jsdelivr": "dist/index.umd.js", + "exports": { + ".": { + "import": { + "types": "./dist/browser/index.d.ts", + "default": "./dist/index.es.js" + }, + "require": { + "types": "./dist/index.d.ts", + "default": "./dist/index.cjs" + } + }, + "./node": { + "types": "./dist/index.d.ts", + "default": "./dist/index.cjs" + }, + "./browser": { + "types": "./dist/browser/index.d.ts", + "default": "./dist/index.es.js" + } + }, + "typesVersions": { + "*": { + "node": [ + "./dist/index.d.ts" + ], + "browser": [ + "./dist/browser/index.d.ts" + ] + } + }, + "files": [ + "dist", + "README.md", + "!tsconfig.tsbuildinfo" + ], + "scripts": { + "dev": "vite", + "clean": "rm -rf dist", + "watch": "tsc --watch", + "build:vite": "vite build", + "build:node": "tsx esbuild.ts", + "typecheck": "tsc --noEmit", + "lint": "eslint . --ext .ts", + "test": "vitest run --dir tests", + "test-ui": "vitest watch --ui", + "prepublishOnly": "pnpm run build", + "build": "pnpm run build:vite && pnpm run build:node" + }, + "keywords": [ + "web3", + "crypto", + "blockchain", + "multiple-chain" + ], + "author": "MultipleChain", + "license": "MIT", + "homepage": "https://github.com/MultipleChain/js/tree/master/packages/networks/sui", + "repository": { + "type": "git", + "url": "git+https://github.com/MultipleChain/js.git" + }, + "bugs": { + "url": "https://github.com/MultipleChain/js/issues" + }, + "dependencies": { + "@kunalabs-io/sui-snap-wallet": "^0.4.3", + "@multiplechain/types": "^0.1.67", + "@multiplechain/utils": "^0.1.21", + "@mysten/sui": "^1.28.2", + "@mysten/wallet-standard": "^0.14.7", + "@suiet/wallet-sdk": "^0.3.3" + } +} diff --git a/packages/networks/sui/pnpm-lock.yaml b/packages/networks/sui/pnpm-lock.yaml new file mode 100644 index 0000000..747994b --- /dev/null +++ b/packages/networks/sui/pnpm-lock.yaml @@ -0,0 +1,1459 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@kunalabs-io/sui-snap-wallet': + specifier: ^0.4.3 + version: 0.4.3(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10) + '@multiplechain/types': + specifier: ^0.1.67 + version: 0.1.70 + '@multiplechain/utils': + specifier: ^0.1.21 + version: 0.1.23(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@mysten/sui': + specifier: ^1.28.2 + version: 1.28.2(typescript@5.8.3) + '@mysten/wallet-standard': + specifier: ^0.14.7 + version: 0.14.7(typescript@5.8.3) + '@suiet/wallet-sdk': + specifier: ^0.3.3 + version: 0.3.3(@mysten/sui@1.28.2(typescript@5.8.3))(typescript@5.8.3) + +packages: + + '@0no-co/graphql.web@1.1.2': + resolution: {integrity: sha512-N2NGsU5FLBhT8NZ+3l2YrzZSHITjNXNuDhC4iDiikv0IujaJ0Xc6xIxQZ/Ek3Cb+rgPjnLHYyJm11tInuJn+cw==} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + peerDependenciesMeta: + graphql: + optional: true + + '@0no-co/graphqlsp@1.12.16': + resolution: {integrity: sha512-B5pyYVH93Etv7xjT6IfB7QtMBdaaC07yjbhN6v8H7KgFStMkPvi+oWYBTibMFRMY89qwc9H8YixXg8SXDVgYWw==} + peerDependencies: + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 + typescript: ^5.0.0 + + '@ethereumjs/common@3.2.0': + resolution: {integrity: sha512-pksvzI0VyLgmuEF2FA/JR/4/y6hcPq8OUail3/AvycBaW1d5VSauOZzqGvJ3RTmR4MU35lWE8KseKOsEhrFRBA==} + + '@ethereumjs/rlp@4.0.1': + resolution: {integrity: sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==} + engines: {node: '>=14'} + hasBin: true + + '@ethereumjs/tx@4.2.0': + resolution: {integrity: sha512-1nc6VO4jtFd172BbSnTnDQVr9IYBFl1y4xPzZdtkrkKIncBCkdbgfdRV+MiTkJYAtTxvV12GRZLqBFT1PNK6Yw==} + engines: {node: '>=14'} + + '@ethereumjs/util@8.1.0': + resolution: {integrity: sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==} + engines: {node: '>=14'} + + '@gql.tada/cli-utils@1.6.3': + resolution: {integrity: sha512-jFFSY8OxYeBxdKi58UzeMXG1tdm4FVjXa8WHIi66Gzu9JWtCE6mqom3a8xkmSw+mVaybFW5EN2WXf1WztJVNyQ==} + peerDependencies: + '@0no-co/graphqlsp': ^1.12.13 + '@gql.tada/svelte-support': 1.0.1 + '@gql.tada/vue-support': 1.0.1 + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 + typescript: ^5.0.0 + peerDependenciesMeta: + '@gql.tada/svelte-support': + optional: true + '@gql.tada/vue-support': + optional: true + + '@gql.tada/internal@1.0.8': + resolution: {integrity: sha512-XYdxJhtHC5WtZfdDqtKjcQ4d7R1s0d1rnlSs3OcBEUbYiPoJJfZU7tWsVXuv047Z6msvmr4ompJ7eLSK5Km57g==} + peerDependencies: + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 + typescript: ^5.0.0 + + '@graphql-typed-document-node/core@3.2.0': + resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@kunalabs-io/sui-snap-wallet@0.4.3': + resolution: {integrity: sha512-cmSbuxTwwif0jmh5M/famqiG/A7hF6TokUXcy6/VA46Wun8pix9839JVr+vp1iLh5T3PvG+KgchSEgJ9vn5Usg==} + + '@metamask/detect-provider@2.0.0': + resolution: {integrity: sha512-sFpN+TX13E9fdBDh9lvQeZdJn4qYoRb/6QF2oZZK/Pn559IhCFacPMU1rMuqyXoFQF3JSJfii2l98B87QDPeCQ==} + engines: {node: '>=14.0.0'} + + '@metamask/json-rpc-engine@7.3.3': + resolution: {integrity: sha512-dwZPq8wx9yV3IX2caLi9q9xZBw2XeIoYqdyihDDDpuHVCEiqadJLwqM3zy+uwf6F1QYQ65A8aOMQg1Uw7LMLNg==} + engines: {node: '>=16.0.0'} + + '@metamask/json-rpc-middleware-stream@6.0.2': + resolution: {integrity: sha512-jtyx3PRfc1kqoLpYveIVQNwsxYKefc64/LCl9h9Da1m3nUKEvypbYuXSIwi237qvOjKmNHQKsDOZg6f4uBf62Q==} + engines: {node: '>=16.0.0'} + + '@metamask/object-multiplex@2.1.0': + resolution: {integrity: sha512-4vKIiv0DQxljcXwfpnbsXcfa5glMj5Zg9mqn4xpIWqkv6uJ2ma5/GtUfLFSxhlxnR8asRMv8dDmWya1Tc1sDFA==} + engines: {node: ^16.20 || ^18.16 || >=20} + + '@metamask/providers@15.0.0': + resolution: {integrity: sha512-FXvL1NQNl6I7fMOJTfQYcBlBZ33vSlm6w80cMpmn8sJh0Lb7wcBpe02UwBsNlARnI+Qsr26XeDs6WHUHQh8CuA==} + engines: {node: ^18.18 || >=20} + + '@metamask/rpc-errors@6.4.0': + resolution: {integrity: sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg==} + engines: {node: '>=16.0.0'} + + '@metamask/safe-event-emitter@3.1.2': + resolution: {integrity: sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA==} + engines: {node: '>=12.0.0'} + + '@metamask/superstruct@3.2.1': + resolution: {integrity: sha512-fLgJnDOXFmuVlB38rUN5SmU7hAFQcCjrg3Vrxz67KTY7YHFnSNEKvX4avmEBdOI0yTCxZjwMCFEqsC8k2+Wd3g==} + engines: {node: '>=16.0.0'} + + '@metamask/utils@8.5.0': + resolution: {integrity: sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==} + engines: {node: '>=16.0.0'} + + '@metamask/utils@9.3.0': + resolution: {integrity: sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==} + engines: {node: '>=16.0.0'} + + '@multiplechain/types@0.1.70': + resolution: {integrity: sha512-o9ovdaefDHE5gorb83avugCjeixfBXrfJkIgSEEcT549yGF8CkmG/jgZIlqXKJf8Lh0tbg4zMAAanUSxUqV1FQ==} + + '@multiplechain/utils@0.1.23': + resolution: {integrity: sha512-6mgJiXQsElObKgX/yA8DUpQYd+BS1GKhAyLj5F/2mv9yY3boZrX8sS7ZLk+g5NX45N+BACNMxbs09TG1iFiRsA==} + + '@mysten/bcs@0.11.1': + resolution: {integrity: sha512-xP85isNSYUCHd3O/g+TmZYmg4wK6cU8q/n/MebkIGP4CYVJZz2wU/G24xIZ3wI+0iTop4dfgA5kYrg/DQKCUzA==} + + '@mysten/bcs@0.7.3': + resolution: {integrity: sha512-fbusBfsyc2MpTACi72H5edWJ670T84va+qn9jSPpb5BzZ+pzUM1Q0ApPrF5OT+mB1o5Ng+mxPQpBCZQkfiV2TA==} + + '@mysten/bcs@1.0.4': + resolution: {integrity: sha512-6JoQi59GN/dVEBCNq8Rj4uOR0niDrJqDx/2gNQWXANwJakHIGH0AMniHrXP41B2dF+mZ3HVmh9Hi3otiEVQTrQ==} + + '@mysten/bcs@1.1.0': + resolution: {integrity: sha512-yy9/1Y4d0FlRywS1+9ze/T7refCbrvwFwJIOKs9M3QBK1njbcHZp+LkVeLqBvIJA5eZ3ZCzmhQ1Xq4Sed5mEBA==} + + '@mysten/bcs@1.6.0': + resolution: {integrity: sha512-ydDRYdIkIFCpHCcPvAkMC91fVwumjzbTgjqds0KsphDQI3jUlH3jFG5lfYNTmV6V3pkhOiRk1fupLBcsQsiszg==} + + '@mysten/sui.js@0.40.0': + resolution: {integrity: sha512-PEGdMe+QgpIdDIpyO4/yb+CK4x3Hki+kYPbQ5n3DVsWyb2ztFwB+5oYdc7qG3QkniO1lnCrlSHqZ5mN+x3RzrQ==} + engines: {node: '>=16'} + deprecated: This package has been renamed to @mysten/sui, please update to use the renamed package. + + '@mysten/sui.js@0.50.1': + resolution: {integrity: sha512-AY0wb4n6PMTRsDGygzrrTHUK/m5KwKZ4aQcN9cayiwsq2iIhfjGo7uuqMA7Y5UiqvLCoF+z7Ig14Q5qejQ/S/w==} + engines: {node: '>=16'} + deprecated: This package has been renamed to @mysten/sui, please update to use the renamed package. + + '@mysten/sui@1.12.0': + resolution: {integrity: sha512-DrSyja04xyGrTGlIQKMwZ6MywxNPkjyIcDLm915Zisoy1/uIgPoHc4cx53JyiG92z/HgowTVGGCCIzH53DIYXA==} + engines: {node: '>=18'} + + '@mysten/sui@1.28.2': + resolution: {integrity: sha512-d+lSp3rAtuOX0taIiIv0KNILDsbmAB9koNGHBinfREraGnE9tUFW315UByuyvuZ9K53ji4i2risdtwxCQ1a8Zw==} + engines: {node: '>=18'} + + '@mysten/sui@1.8.0': + resolution: {integrity: sha512-iL7yztpePS/GWFZ7yiD/Pl7ciuOD2ySyogJZmLFu4WxZfiIcXJX+U/U+Egq9VHvELk8+m+Z1OvvPlNQfuowMIg==} + engines: {node: '>=18'} + + '@mysten/utils@0.0.0': + resolution: {integrity: sha512-KRI57Qow3E7TGqczimazwGf7+fwukdOi+6a31igSCzz0kPjAXbyK1a1gXaxeLMF8xEZ07ouW3RnsWt+EaUuHUw==} + + '@mysten/wallet-adapter-base@0.9.0': + resolution: {integrity: sha512-Obcfd30AC0Tcyvqc9+h0vvjrRB6Td2PNWhxRlTq/hSxDY66M3XqTLgoO8wDBtz+91oga4obAYvM02m7xV7X0Zw==} + deprecated: Wallet adapters have been deprecated in favor of the Wallet Standard. Please upgrade to the latest Wallet Kit versions. + + '@mysten/wallet-standard@0.10.3': + resolution: {integrity: sha512-StEMbJ7YkdjxoScmOJJjt9JaNgoRLD3FgU4LGFrKFtMXOhPu1Z4WF3qgfYBr+C3UgDh+JNiDIfN9TiGeM/Ahpw==} + + '@mysten/wallet-standard@0.13.3': + resolution: {integrity: sha512-aLxhLIM6uzsfBZ5HbOLrvw1WrzHGPzysUmiFrXRizNpclz2DqxeqngDiDq8VogKM2bXCjhF0SxSc+Bj+relp7w==} + + '@mysten/wallet-standard@0.13.7': + resolution: {integrity: sha512-FXlqn3Gp4E7aQf33rZQfaCEUeEq9TbmkIFA7kX/Yab5SC5892XVhkLRu040eBs8Cest98jFUZ2ZJ4YWR+a7e5g==} + + '@mysten/wallet-standard@0.14.7': + resolution: {integrity: sha512-0X97MBDdbRyobm4mLvwqMW30t5nN6njLU9roN4bcugFiQGXtcyTA6oyoFgpeXCjNGTmamNOnLwsBJ/A8Iet/jw==} + + '@mysten/wallet-standard@0.6.0': + resolution: {integrity: sha512-xE/OijN9zIPoTjTWuxlYMHtp7kXPcAR8dDAbxOIH5h7EZCTk+G6p+SzDp8jV5magf50VcYP0cVjS4CQLx1wQuQ==} + + '@mysten/zksend@0.11.0': + resolution: {integrity: sha512-Q44ljYpH2Om8kOu/P+gxhZjFh8HgGXM3fdvpv3u0Fh+IbZW0ypv6G2RGDwldWBXY+pYO4xVpxres8QfC4CzALQ==} + + '@noble/curves@1.4.2': + resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==} + + '@noble/curves@1.8.2': + resolution: {integrity: sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.9.0': + resolution: {integrity: sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.3.0': + resolution: {integrity: sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==} + + '@noble/hashes@1.4.0': + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} + + '@noble/hashes@1.7.2': + resolution: {integrity: sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@open-rpc/client-js@1.8.1': + resolution: {integrity: sha512-vV+Hetl688nY/oWI9IFY0iKDrWuLdYhf7OIKI6U1DcnJV7r4gAgwRJjEr1QVYszUc0gjkHoQJzqevmXMGLyA0g==} + + '@scure/base@1.1.9': + resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} + + '@scure/base@1.2.4': + resolution: {integrity: sha512-5Yy9czTO47mqz+/J8GM6GIId4umdCk1wc1q8rKERQulIoc8VP9pzDcghv10Tl2E7R96ZUx/PhND3ESYUQX8NuQ==} + + '@scure/bip32@1.4.0': + resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} + + '@scure/bip32@1.6.2': + resolution: {integrity: sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==} + + '@scure/bip39@1.3.0': + resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} + + '@scure/bip39@1.5.4': + resolution: {integrity: sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==} + + '@suchipi/femver@1.0.0': + resolution: {integrity: sha512-bprE8+K5V+DPX7q2e2K57ImqNBdfGHDIWaGI5xHxZoxbKOuQZn4wzPiUxOAHnsUr3w3xHrWXwN7gnG/iIuEMIg==} + + '@suiet/wallet-sdk@0.3.3': + resolution: {integrity: sha512-Mg4MdnTmQT6vnLFwP3Fo7PHEPhEb7YDF3EnIPov8ACBA5xZq4WZV0GKjFRJujm171EjSGKyIDO5T4FaQb1GFHA==} + peerDependencies: + '@mysten/sui': 1.12.0 + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@22.14.1': + resolution: {integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + '@wallet-standard/app@1.1.0': + resolution: {integrity: sha512-3CijvrO9utx598kjr45hTbbeeykQrQfKmSnxeWOgU25TOEpvcipD/bYDQWIqUv1Oc6KK4YStokSMu/FBNecGUQ==} + engines: {node: '>=16'} + + '@wallet-standard/base@1.1.0': + resolution: {integrity: sha512-DJDQhjKmSNVLKWItoKThJS+CsJQjR9AOBOirBVT1F9YpRyC9oYHE+ZnSf8y8bxUphtKqdQMPVQ2mHohYdRvDVQ==} + engines: {node: '>=16'} + + '@wallet-standard/core@1.0.3': + resolution: {integrity: sha512-Jb33IIjC1wM1HoKkYD7xQ6d6PZ8EmMZvyc8R7dFgX66n/xkvksVTW04g9yLvQXrLFbcIjHrCxW6TXMhvpsAAzg==} + engines: {node: '>=16'} + + '@wallet-standard/core@1.1.0': + resolution: {integrity: sha512-v2W5q/NlX1qkn2q/JOXQT//pOAdrhz7+nOcO2uiH9+a0uvreL+sdWWqkhFmMcX+HEBjaibdOQMUoIfDhOGX4XA==} + engines: {node: '>=16'} + + '@wallet-standard/errors@0.1.1': + resolution: {integrity: sha512-V8Ju1Wvol8i/VDyQOHhjhxmMVwmKiwyxUZBnHhtiPZJTWY0U/Shb2iEWyGngYEbAkp2sGTmEeNX1tVyGR7PqNw==} + engines: {node: '>=16'} + hasBin: true + + '@wallet-standard/features@1.1.0': + resolution: {integrity: sha512-hiEivWNztx73s+7iLxsuD1sOJ28xtRix58W7Xnz4XzzA/pF0+aicnWgjOdA10doVDEDZdUuZCIIqG96SFNlDUg==} + engines: {node: '>=16'} + + '@wallet-standard/wallet@1.1.0': + resolution: {integrity: sha512-Gt8TnSlDZpAl+RWOOAB/kuvC7RpcdWAlFbHNoi4gsXsfaWa1QCT6LBcfIYTPdOZC9OVZUDwqGuGAcqZejDmHjg==} + engines: {node: '>=16'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + base-x@4.0.1: + resolution: {integrity: sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw==} + + base-x@5.0.1: + resolution: {integrity: sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bech32@2.0.0: + resolution: {integrity: sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==} + + bignumber.js@9.3.0: + resolution: {integrity: sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==} + + bs58@5.0.0: + resolution: {integrity: sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==} + + bs58@6.0.0: + resolution: {integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + bufferutil@4.0.9: + resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==} + engines: {node: '>=6.14.2'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + chalk@5.4.1: + resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + detect-browser@5.3.0: + resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + ethereum-cryptography@2.2.1: + resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + extension-port-stream@3.0.0: + resolution: {integrity: sha512-an2S5quJMiy5bnZKEf6AkfH/7r8CzHvhchU40gxN+OM6HPhe7Z9T1FUychcf2M9PpPOO0Hf7BAEfJkw2TDIBDw==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + gql.tada@1.8.10: + resolution: {integrity: sha512-FrvSxgz838FYVPgZHGOSgbpOjhR+yq44rCzww3oOPJYi0OvBJjAgCiP6LEokZIYND2fUTXzQAyLgcvgw1yNP5A==} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + + graphql@16.10.0: + resolution: {integrity: sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + isomorphic-fetch@3.0.0: + resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} + + isomorphic-ws@5.0.0: + resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} + peerDependencies: + ws: '*' + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + micro-ftch@0.3.1: + resolution: {integrity: sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanostores@0.10.3: + resolution: {integrity: sha512-Nii8O1XqmawqSCf9o2aWqVxhKRN01+iue9/VEd1TiJCr9VT5XxgPFbF1Edl1XN6pwJcZRsl8Ki+z01yb/T/C2g==} + engines: {node: ^18.0.0 || >=20.0.0} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + pony-cause@2.1.11: + resolution: {integrity: sha512-M7LhCsdNbNgiLYiP4WjsfLUuFmCfnjdF6jKe2R9NKl4WFN+HZPGHJZ9lnLP7f9ZnKe3U9nuWD0szirmj+migUg==} + engines: {node: '>=12.0.0'} + + poseidon-lite@0.2.1: + resolution: {integrity: sha512-xIr+G6HeYfOhCuswdqcFpSX47SPhm0EpisWJ6h7fHlWwaVIvH3dLnejpatrtw6Xc6HaLrpq05y7VRfvDmDGIog==} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + strict-event-emitter-types@2.0.0: + resolution: {integrity: sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + superstruct@1.0.3: + resolution: {integrity: sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==} + engines: {node: '>=14.0.0'} + + superstruct@1.0.4: + resolution: {integrity: sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ==} + engines: {node: '>=14.0.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tweetnacl@1.0.3: + resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + utf-8-validate@5.0.10: + resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} + engines: {node: '>=6.14.2'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + + valibot@0.36.0: + resolution: {integrity: sha512-CjF1XN4sUce8sBK9TixrDqFM7RwNkuXdJu174/AwmQUB62QbCQADg5lLe8ldBalFgtj1uKj+pKwDJiNo4Mn+eQ==} + + web3-errors@1.3.1: + resolution: {integrity: sha512-w3NMJujH+ZSW4ltIZZKtdbkbyQEvBzyp3JRn59Ckli0Nz4VMsVq8aF1bLWM7A2kuQ+yVEm3ySeNU+7mSRwx7RQ==} + engines: {node: '>=14', npm: '>=6.12.0'} + + web3-types@1.10.0: + resolution: {integrity: sha512-0IXoaAFtFc8Yin7cCdQfB9ZmjafrbP6BO0f0KT/khMhXKUpoJ6yShrVhiNpyRBo8QQjuOagsWzwSK2H49I7sbw==} + engines: {node: '>=14', npm: '>=6.12.0'} + + web3-utils@4.3.3: + resolution: {integrity: sha512-kZUeCwaQm+RNc2Bf1V3BYbF29lQQKz28L0y+FA4G0lS8IxtJVGi5SeDTUkpwqqkdHHC7JcapPDnyyzJ1lfWlOw==} + engines: {node: '>=14', npm: '>=6.12.0'} + + web3-validator@2.0.6: + resolution: {integrity: sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg==} + engines: {node: '>=14', npm: '>=6.12.0'} + + webextension-polyfill@0.10.0: + resolution: {integrity: sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.18.1: + resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + zod@3.24.3: + resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==} + +snapshots: + + '@0no-co/graphql.web@1.1.2(graphql@16.10.0)': + optionalDependencies: + graphql: 16.10.0 + + '@0no-co/graphqlsp@1.12.16(graphql@16.10.0)(typescript@5.8.3)': + dependencies: + '@gql.tada/internal': 1.0.8(graphql@16.10.0)(typescript@5.8.3) + graphql: 16.10.0 + typescript: 5.8.3 + + '@ethereumjs/common@3.2.0': + dependencies: + '@ethereumjs/util': 8.1.0 + crc-32: 1.2.2 + + '@ethereumjs/rlp@4.0.1': {} + + '@ethereumjs/tx@4.2.0': + dependencies: + '@ethereumjs/common': 3.2.0 + '@ethereumjs/rlp': 4.0.1 + '@ethereumjs/util': 8.1.0 + ethereum-cryptography: 2.2.1 + + '@ethereumjs/util@8.1.0': + dependencies: + '@ethereumjs/rlp': 4.0.1 + ethereum-cryptography: 2.2.1 + micro-ftch: 0.3.1 + + '@gql.tada/cli-utils@1.6.3(@0no-co/graphqlsp@1.12.16(graphql@16.10.0)(typescript@5.8.3))(graphql@16.10.0)(typescript@5.8.3)': + dependencies: + '@0no-co/graphqlsp': 1.12.16(graphql@16.10.0)(typescript@5.8.3) + '@gql.tada/internal': 1.0.8(graphql@16.10.0)(typescript@5.8.3) + graphql: 16.10.0 + typescript: 5.8.3 + + '@gql.tada/internal@1.0.8(graphql@16.10.0)(typescript@5.8.3)': + dependencies: + '@0no-co/graphql.web': 1.1.2(graphql@16.10.0) + graphql: 16.10.0 + typescript: 5.8.3 + + '@graphql-typed-document-node/core@3.2.0(graphql@16.10.0)': + dependencies: + graphql: 16.10.0 + + '@kunalabs-io/sui-snap-wallet@0.4.3(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)': + dependencies: + '@metamask/detect-provider': 2.0.0 + '@metamask/providers': 15.0.0 + '@mysten/sui.js': 0.50.1(typescript@5.8.3) + '@mysten/wallet-adapter-base': 0.9.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@mysten/wallet-standard': 0.10.3(typescript@5.8.3) + superstruct: 1.0.4 + transitivePeerDependencies: + - '@gql.tada/svelte-support' + - '@gql.tada/vue-support' + - bufferutil + - encoding + - supports-color + - typescript + - utf-8-validate + + '@metamask/detect-provider@2.0.0': {} + + '@metamask/json-rpc-engine@7.3.3': + dependencies: + '@metamask/rpc-errors': 6.4.0 + '@metamask/safe-event-emitter': 3.1.2 + '@metamask/utils': 8.5.0 + transitivePeerDependencies: + - supports-color + + '@metamask/json-rpc-middleware-stream@6.0.2': + dependencies: + '@metamask/json-rpc-engine': 7.3.3 + '@metamask/safe-event-emitter': 3.1.2 + '@metamask/utils': 8.5.0 + readable-stream: 3.6.2 + transitivePeerDependencies: + - supports-color + + '@metamask/object-multiplex@2.1.0': + dependencies: + once: 1.4.0 + readable-stream: 3.6.2 + + '@metamask/providers@15.0.0': + dependencies: + '@metamask/json-rpc-engine': 7.3.3 + '@metamask/json-rpc-middleware-stream': 6.0.2 + '@metamask/object-multiplex': 2.1.0 + '@metamask/rpc-errors': 6.4.0 + '@metamask/safe-event-emitter': 3.1.2 + '@metamask/utils': 8.5.0 + detect-browser: 5.3.0 + extension-port-stream: 3.0.0 + fast-deep-equal: 3.1.3 + is-stream: 2.0.1 + readable-stream: 3.6.2 + webextension-polyfill: 0.10.0 + transitivePeerDependencies: + - supports-color + + '@metamask/rpc-errors@6.4.0': + dependencies: + '@metamask/utils': 9.3.0 + fast-safe-stringify: 2.1.1 + transitivePeerDependencies: + - supports-color + + '@metamask/safe-event-emitter@3.1.2': {} + + '@metamask/superstruct@3.2.1': {} + + '@metamask/utils@8.5.0': + dependencies: + '@ethereumjs/tx': 4.2.0 + '@metamask/superstruct': 3.2.1 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.4 + '@types/debug': 4.1.12 + debug: 4.4.0 + pony-cause: 2.1.11 + semver: 7.7.1 + uuid: 9.0.1 + transitivePeerDependencies: + - supports-color + + '@metamask/utils@9.3.0': + dependencies: + '@ethereumjs/tx': 4.2.0 + '@metamask/superstruct': 3.2.1 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.4 + '@types/debug': 4.1.12 + debug: 4.4.0 + pony-cause: 2.1.11 + semver: 7.7.1 + uuid: 9.0.1 + transitivePeerDependencies: + - supports-color + + '@multiplechain/types@0.1.70': {} + + '@multiplechain/utils@0.1.23(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + '@types/ws': 8.18.1 + bignumber.js: 9.3.0 + web3-utils: 4.3.3 + ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@mysten/bcs@0.11.1': + dependencies: + bs58: 5.0.0 + + '@mysten/bcs@0.7.3': + dependencies: + bs58: 5.0.0 + + '@mysten/bcs@1.0.4': + dependencies: + bs58: 6.0.0 + + '@mysten/bcs@1.1.0': + dependencies: + bs58: 6.0.0 + + '@mysten/bcs@1.6.0': + dependencies: + '@scure/base': 1.2.4 + + '@mysten/sui.js@0.40.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + '@mysten/bcs': 0.7.3 + '@noble/curves': 1.9.0 + '@noble/hashes': 1.8.0 + '@open-rpc/client-js': 1.8.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@scure/bip32': 1.6.2 + '@scure/bip39': 1.5.4 + '@suchipi/femver': 1.0.0 + events: 3.3.0 + superstruct: 1.0.4 + tweetnacl: 1.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + + '@mysten/sui.js@0.50.1(typescript@5.8.3)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.10.0) + '@mysten/bcs': 0.11.1 + '@noble/curves': 1.9.0 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.6.2 + '@scure/bip39': 1.5.4 + '@suchipi/femver': 1.0.0 + bech32: 2.0.0 + gql.tada: 1.8.10(graphql@16.10.0)(typescript@5.8.3) + graphql: 16.10.0 + superstruct: 1.0.4 + tweetnacl: 1.0.3 + transitivePeerDependencies: + - '@gql.tada/svelte-support' + - '@gql.tada/vue-support' + - typescript + + '@mysten/sui@1.12.0(typescript@5.8.3)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.10.0) + '@mysten/bcs': 1.1.0 + '@noble/curves': 1.9.0 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.6.2 + '@scure/bip39': 1.5.4 + '@suchipi/femver': 1.0.0 + bech32: 2.0.0 + gql.tada: 1.8.10(graphql@16.10.0)(typescript@5.8.3) + graphql: 16.10.0 + tweetnacl: 1.0.3 + valibot: 0.36.0 + transitivePeerDependencies: + - '@gql.tada/svelte-support' + - '@gql.tada/vue-support' + - typescript + + '@mysten/sui@1.28.2(typescript@5.8.3)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.10.0) + '@mysten/bcs': 1.6.0 + '@mysten/utils': 0.0.0 + '@noble/curves': 1.9.0 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.4 + '@scure/bip32': 1.6.2 + '@scure/bip39': 1.5.4 + gql.tada: 1.8.10(graphql@16.10.0)(typescript@5.8.3) + graphql: 16.10.0 + poseidon-lite: 0.2.1 + valibot: 0.36.0 + transitivePeerDependencies: + - '@gql.tada/svelte-support' + - '@gql.tada/vue-support' + - typescript + + '@mysten/sui@1.8.0(typescript@5.8.3)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.10.0) + '@mysten/bcs': 1.0.4 + '@noble/curves': 1.9.0 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.6.2 + '@scure/bip39': 1.5.4 + '@suchipi/femver': 1.0.0 + bech32: 2.0.0 + gql.tada: 1.8.10(graphql@16.10.0)(typescript@5.8.3) + graphql: 16.10.0 + tweetnacl: 1.0.3 + valibot: 0.36.0 + transitivePeerDependencies: + - '@gql.tada/svelte-support' + - '@gql.tada/vue-support' + - typescript + + '@mysten/utils@0.0.0': + dependencies: + '@scure/base': 1.2.4 + + '@mysten/wallet-adapter-base@0.9.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + '@mysten/sui.js': 0.40.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@mysten/wallet-standard': 0.6.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + + '@mysten/wallet-standard@0.10.3(typescript@5.8.3)': + dependencies: + '@mysten/sui.js': 0.50.1(typescript@5.8.3) + '@wallet-standard/core': 1.0.3 + transitivePeerDependencies: + - '@gql.tada/svelte-support' + - '@gql.tada/vue-support' + - typescript + + '@mysten/wallet-standard@0.13.3(typescript@5.8.3)': + dependencies: + '@mysten/sui': 1.8.0(typescript@5.8.3) + '@wallet-standard/core': 1.0.3 + transitivePeerDependencies: + - '@gql.tada/svelte-support' + - '@gql.tada/vue-support' + - typescript + + '@mysten/wallet-standard@0.13.7(typescript@5.8.3)': + dependencies: + '@mysten/sui': 1.12.0(typescript@5.8.3) + '@wallet-standard/core': 1.0.3 + transitivePeerDependencies: + - '@gql.tada/svelte-support' + - '@gql.tada/vue-support' + - typescript + + '@mysten/wallet-standard@0.14.7(typescript@5.8.3)': + dependencies: + '@mysten/sui': 1.28.2(typescript@5.8.3) + '@wallet-standard/core': 1.1.0 + transitivePeerDependencies: + - '@gql.tada/svelte-support' + - '@gql.tada/vue-support' + - typescript + + '@mysten/wallet-standard@0.6.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + '@mysten/sui.js': 0.40.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@wallet-standard/core': 1.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + + '@mysten/zksend@0.11.0(typescript@5.8.3)': + dependencies: + '@mysten/sui': 1.8.0(typescript@5.8.3) + '@mysten/wallet-standard': 0.13.3(typescript@5.8.3) + mitt: 3.0.1 + nanostores: 0.10.3 + valibot: 0.36.0 + transitivePeerDependencies: + - '@gql.tada/svelte-support' + - '@gql.tada/vue-support' + - typescript + + '@noble/curves@1.4.2': + dependencies: + '@noble/hashes': 1.4.0 + + '@noble/curves@1.8.2': + dependencies: + '@noble/hashes': 1.7.2 + + '@noble/curves@1.9.0': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/hashes@1.3.0': {} + + '@noble/hashes@1.4.0': {} + + '@noble/hashes@1.7.2': {} + + '@noble/hashes@1.8.0': {} + + '@open-rpc/client-js@1.8.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + isomorphic-fetch: 3.0.0 + isomorphic-ws: 5.0.0(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + strict-event-emitter-types: 2.0.0 + ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + + '@scure/base@1.1.9': {} + + '@scure/base@1.2.4': {} + + '@scure/bip32@1.4.0': + dependencies: + '@noble/curves': 1.4.2 + '@noble/hashes': 1.4.0 + '@scure/base': 1.1.9 + + '@scure/bip32@1.6.2': + dependencies: + '@noble/curves': 1.8.2 + '@noble/hashes': 1.7.2 + '@scure/base': 1.2.4 + + '@scure/bip39@1.3.0': + dependencies: + '@noble/hashes': 1.4.0 + '@scure/base': 1.1.9 + + '@scure/bip39@1.5.4': + dependencies: + '@noble/hashes': 1.7.2 + '@scure/base': 1.2.4 + + '@suchipi/femver@1.0.0': {} + + '@suiet/wallet-sdk@0.3.3(@mysten/sui@1.28.2(typescript@5.8.3))(typescript@5.8.3)': + dependencies: + '@mysten/sui': 1.28.2(typescript@5.8.3) + '@mysten/wallet-standard': 0.13.7(typescript@5.8.3) + '@mysten/zksend': 0.11.0(typescript@5.8.3) + '@noble/hashes': 1.3.0 + buffer: 6.0.3 + superstruct: 1.0.3 + tweetnacl: 1.0.3 + transitivePeerDependencies: + - '@gql.tada/svelte-support' + - '@gql.tada/vue-support' + - typescript + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/ms@2.1.0': {} + + '@types/node@22.14.1': + dependencies: + undici-types: 6.21.0 + + '@types/ws@8.18.1': + dependencies: + '@types/node': 22.14.1 + + '@wallet-standard/app@1.1.0': + dependencies: + '@wallet-standard/base': 1.1.0 + + '@wallet-standard/base@1.1.0': {} + + '@wallet-standard/core@1.0.3': + dependencies: + '@wallet-standard/app': 1.1.0 + '@wallet-standard/base': 1.1.0 + '@wallet-standard/features': 1.1.0 + '@wallet-standard/wallet': 1.1.0 + + '@wallet-standard/core@1.1.0': + dependencies: + '@wallet-standard/app': 1.1.0 + '@wallet-standard/base': 1.1.0 + '@wallet-standard/errors': 0.1.1 + '@wallet-standard/features': 1.1.0 + '@wallet-standard/wallet': 1.1.0 + + '@wallet-standard/errors@0.1.1': + dependencies: + chalk: 5.4.1 + commander: 13.1.0 + + '@wallet-standard/features@1.1.0': + dependencies: + '@wallet-standard/base': 1.1.0 + + '@wallet-standard/wallet@1.1.0': + dependencies: + '@wallet-standard/base': 1.1.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + base-x@4.0.1: {} + + base-x@5.0.1: {} + + base64-js@1.5.1: {} + + bech32@2.0.0: {} + + bignumber.js@9.3.0: {} + + bs58@5.0.0: + dependencies: + base-x: 4.0.1 + + bs58@6.0.0: + dependencies: + base-x: 5.0.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bufferutil@4.0.9: + dependencies: + node-gyp-build: 4.8.4 + optional: true + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + chalk@5.4.1: {} + + commander@13.1.0: {} + + crc-32@1.2.2: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + detect-browser@5.3.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + ethereum-cryptography@2.2.1: + dependencies: + '@noble/curves': 1.4.2 + '@noble/hashes': 1.4.0 + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + + eventemitter3@5.0.1: {} + + events@3.3.0: {} + + extension-port-stream@3.0.0: + dependencies: + readable-stream: 3.6.2 + webextension-polyfill: 0.10.0 + + fast-deep-equal@3.1.3: {} + + fast-safe-stringify@2.1.1: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + gql.tada@1.8.10(graphql@16.10.0)(typescript@5.8.3): + dependencies: + '@0no-co/graphql.web': 1.1.2(graphql@16.10.0) + '@0no-co/graphqlsp': 1.12.16(graphql@16.10.0)(typescript@5.8.3) + '@gql.tada/cli-utils': 1.6.3(@0no-co/graphqlsp@1.12.16(graphql@16.10.0)(typescript@5.8.3))(graphql@16.10.0)(typescript@5.8.3) + '@gql.tada/internal': 1.0.8(graphql@16.10.0)(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - '@gql.tada/svelte-support' + - '@gql.tada/vue-support' + - graphql + + graphql@16.10.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + ieee754@1.2.1: {} + + inherits@2.0.4: {} + + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-stream@2.0.1: {} + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + isomorphic-fetch@3.0.0: + dependencies: + node-fetch: 2.7.0 + whatwg-fetch: 3.6.20 + transitivePeerDependencies: + - encoding + + isomorphic-ws@5.0.0(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + dependencies: + ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10) + + math-intrinsics@1.1.0: {} + + micro-ftch@0.3.1: {} + + mitt@3.0.1: {} + + ms@2.1.3: {} + + nanostores@0.10.3: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-gyp-build@4.8.4: + optional: true + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + pony-cause@2.1.11: {} + + poseidon-lite@0.2.1: {} + + possible-typed-array-names@1.1.0: {} + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + safe-buffer@5.2.1: {} + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + semver@7.7.1: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + strict-event-emitter-types@2.0.0: {} + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + superstruct@1.0.3: {} + + superstruct@1.0.4: {} + + tr46@0.0.3: {} + + tweetnacl@1.0.3: {} + + typescript@5.8.3: {} + + undici-types@6.21.0: {} + + utf-8-validate@5.0.10: + dependencies: + node-gyp-build: 4.8.4 + optional: true + + util-deprecate@1.0.2: {} + + util@0.12.5: + dependencies: + inherits: 2.0.4 + is-arguments: 1.2.0 + is-generator-function: 1.1.0 + is-typed-array: 1.1.15 + which-typed-array: 1.1.19 + + uuid@9.0.1: {} + + valibot@0.36.0: {} + + web3-errors@1.3.1: + dependencies: + web3-types: 1.10.0 + + web3-types@1.10.0: {} + + web3-utils@4.3.3: + dependencies: + ethereum-cryptography: 2.2.1 + eventemitter3: 5.0.1 + web3-errors: 1.3.1 + web3-types: 1.10.0 + web3-validator: 2.0.6 + + web3-validator@2.0.6: + dependencies: + ethereum-cryptography: 2.2.1 + util: 0.12.5 + web3-errors: 1.3.1 + web3-types: 1.10.0 + zod: 3.24.3 + + webextension-polyfill@0.10.0: {} + + webidl-conversions@3.0.1: {} + + whatwg-fetch@3.6.20: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + wrappy@1.0.2: {} + + ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10): + optionalDependencies: + bufferutil: 4.0.9 + utf-8-validate: 5.0.10 + + ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10): + optionalDependencies: + bufferutil: 4.0.9 + utf-8-validate: 5.0.10 + + zod@3.24.3: {} diff --git a/packages/networks/sui/src/assets/Coin.ts b/packages/networks/sui/src/assets/Coin.ts new file mode 100644 index 0000000..81e3a0d --- /dev/null +++ b/packages/networks/sui/src/assets/Coin.ts @@ -0,0 +1,85 @@ +import { fromMist, tomMist } from '../utils' +import { Provider } from '../services/Provider' +import { SUI_DECIMALS } from '@mysten/sui/utils' +import { TransactionSigner } from '../services/TransactionSigner' +import { + ErrorTypeEnum, + type CoinInterface, + type TransferAmount, + type WalletAddress +} from '@multiplechain/types' +import { Transaction } from '@mysten/sui/transactions' + +export class Coin implements CoinInterface { + /** + * Blockchain network provider + */ + provider: Provider + + /** + * @param provider network provider + */ + constructor(provider?: Provider) { + this.provider = provider ?? Provider.instance + } + + /** + * @returns Coin name + */ + getName(): string { + return 'Sui' + } + + /** + * @returns Coin symbol + */ + getSymbol(): string { + return 'SUI' + } + + /** + * @returns Decimal value of the coin + */ + getDecimals(): number { + return SUI_DECIMALS + } + + /** + * @param owner Wallet address + * @returns Wallet balance as currency of COIN + */ + async getBalance(owner: WalletAddress): Promise { + const balance = await this.provider.client.getBalance({ + owner + }) + return fromMist(balance.totalBalance) + } + + /** + * @param sender Sender wallet address + * @param receiver Receiver wallet address + * @param amount Amount of assets that will be transferred + * @returns Transaction signer + */ + async transfer( + sender: WalletAddress, + receiver: WalletAddress, + amount: TransferAmount + ): Promise { + if (amount < 0) { + throw new Error(ErrorTypeEnum.INVALID_AMOUNT) + } + + if (amount > (await this.getBalance(sender))) { + throw new Error(ErrorTypeEnum.INSUFFICIENT_BALANCE) + } + + const tx = new Transaction() + + const [coin] = tx.splitCoins(tx.gas, [tomMist(amount)]) + + tx.transferObjects([coin], receiver) + + return new TransactionSigner(tx) + } +} diff --git a/packages/networks/sui/src/assets/Contract.ts b/packages/networks/sui/src/assets/Contract.ts new file mode 100644 index 0000000..8ba59c1 --- /dev/null +++ b/packages/networks/sui/src/assets/Contract.ts @@ -0,0 +1,80 @@ +import { Provider } from '../services/Provider' +import type { ContractAddress, ContractInterface, WalletAddress } from '@multiplechain/types' + +export class Contract implements ContractInterface { + /** + * Contract address + */ + address: ContractAddress + + /** + * Cached static methods + */ + cachedMethods: Record = {} + + /** + * Blockchain network provider + */ + provider: Provider + + /** + * @param address Contract address + * @param provider Blockchain network provider + */ + constructor(address: ContractAddress, provider?: Provider) { + this.address = address + this.provider = provider ?? Provider.instance + } + + /** + * @returns Contract address + */ + getAddress(): ContractAddress { + return this.address + } + + /** + * @param _method Method name + * @param _args Method parameters + * @returns Method result + */ + async callMethod(_method: string, ..._args: unknown[]): Promise { + throw new Error('Method not implemented.') + } + + /** + * @param method Method name + * @param args Method parameters + * @returns Method result + */ + async callMethodWithCache(method: string, ...args: unknown[]): Promise { + if (this.cachedMethods[method] !== undefined) { + return this.cachedMethods[method] + } + + return (this.cachedMethods[method] = await this.callMethod(method, ...args)) + } + + /** + * @param _method Method name + * @param _args Sender wallet address + * @returns Encoded method data + */ + async getMethodData(_method: string, ..._args: unknown[]): Promise { + throw new Error('Method not implemented.') + } + + /** + * @param _method Method name + * @param _from Sender wallet address + * @param _args Method parameters + * @returns Encoded method data + */ + async createTransactionData( + _method: string, + _from: WalletAddress, + ..._args: any[] + ): Promise { + throw new Error('Method not implemented.') + } +} diff --git a/packages/networks/sui/src/assets/NFT.ts b/packages/networks/sui/src/assets/NFT.ts new file mode 100644 index 0000000..5dc6200 --- /dev/null +++ b/packages/networks/sui/src/assets/NFT.ts @@ -0,0 +1,167 @@ +import { Contract } from './Contract' +import { TransactionSigner } from '../services/TransactionSigner' +import { + ErrorTypeEnum, + type NftId, + type NftInterface, + type WalletAddress +} from '@multiplechain/types' +import { Transaction } from '@mysten/sui/transactions' + +interface NftMetadata { + name: string + symbol: string + owner: string | null + image: string | null + description: string +} + +export class NFT extends Contract implements NftInterface { + /** + * Contract metadata + */ + metadata: NftMetadata | null + + /** + * @param address NFT address + * @returns Contract metadata + */ + async getMetadata(address?: string): Promise { + const res = await this.provider.client.getObject({ + id: address ?? this.address, + options: { + showContent: true, + showOwner: true + } + }) + if (res?.data?.content?.dataType === 'moveObject') { + const obj = res.data.content.fields as any + this.metadata = { + name: obj.name, + symbol: obj.symbol ?? obj.name, + description: obj.description ?? obj.name, + image: obj.image ?? obj.url ?? obj.image_url ?? null, + // @ts-expect-error it's possible + owner: res.data.owner?.ObjectOwner ?? res.data.owner?.AddressOwner ?? null + } + } + return this.metadata + } + + /** + * @param address NFT address + * @returns NFT name + */ + async getName(address?: string): Promise { + return (await this.getMetadata(address))?.name ?? '' + } + + /** + * @param address NFT address + * @returns NFT symbol + */ + async getSymbol(address?: string): Promise { + return (await this.getMetadata(address))?.symbol ?? '' + } + + /** + * @param owner Wallet address + * @returns Wallet balance as currency of NFT + */ + async getBalance(owner: WalletAddress): Promise { + const res = await this.provider.client.getOwnedObjects({ + owner, + filter: { + StructType: this.address + }, + limit: 50 + }) + return res.data.length + } + + /** + * @param nftId NFT ID + * @returns Wallet address of the owner of the NFT + */ + async getOwner(nftId: NftId): Promise { + return (await this.getMetadata(String(nftId)))?.owner ?? '' + } + + /** + * @param nftId NFT ID + * @returns URI of the NFT + */ + async getTokenURI(nftId: NftId): Promise { + return (await this.getMetadata(String(nftId)))?.image ?? '' + } + + /** + * @param _nftId ID of the NFT that will be transferred + * @returns Wallet address of the approved spender + */ + async getApproved(_nftId: NftId): Promise { + throw new Error('Method not implemented.') + } + + /** + * @param sender Sender address + * @param receiver Receiver address + * @param nftId NFT ID + * @returns Transaction signer + */ + async transfer( + sender: WalletAddress, + receiver: WalletAddress, + nftId: NftId + ): Promise { + // Check if tokens exist + const balance = await this.getBalance(sender) + + if (balance <= 0) { + throw new Error(ErrorTypeEnum.INSUFFICIENT_BALANCE) + } + + // Check ownership + const originalOwner = await this.getOwner(nftId) + if (originalOwner.toLowerCase() !== sender.toLowerCase()) { + throw new Error(ErrorTypeEnum.UNAUTHORIZED_ADDRESS) + } + + const tx = new Transaction() + + tx.transferObjects([tx.object(String(nftId))], receiver) + + return new TransactionSigner(tx) + } + + /** + * @param _spender Spender address + * @param _owner Owner address + * @param _receiver Receiver address + * @param _nftId NFT ID + * @returns Transaction signer + */ + async transferFrom( + _spender: WalletAddress, + _owner: WalletAddress, + _receiver: WalletAddress, + _nftId: NftId + ): Promise { + throw new Error('Method not implemented.') + } + + /** + * Gives permission to the spender to spend owner's tokens + * @param _owner Address of owner of the tokens that will be used + * @param _spender Address of the spender that will use the tokens of owner + * @param _nftId ID of the NFT that will be transferred + * @returns Transaction signer + */ + async approve( + _owner: WalletAddress, + _spender: WalletAddress, + _nftId: NftId + ): Promise { + throw new Error('Method not implemented.') + } +} diff --git a/packages/networks/sui/src/assets/Token.ts b/packages/networks/sui/src/assets/Token.ts new file mode 100644 index 0000000..8afb8ca --- /dev/null +++ b/packages/networks/sui/src/assets/Token.ts @@ -0,0 +1,186 @@ +import { Contract } from './Contract' +import { math } from '@multiplechain/utils' +import { TransactionSigner } from '../services/TransactionSigner' +import { + ErrorTypeEnum, + type TokenInterface, + type TransferAmount, + type WalletAddress +} from '@multiplechain/types' +import { Transaction } from '@mysten/sui/transactions' +import type { CoinMetadata } from '@mysten/sui/client' + +export class Token extends Contract implements TokenInterface { + /** + * Contract metadata + */ + metadata: CoinMetadata | null + + /** + * @returns Contract metadata + */ + async getMetadata(): Promise { + if (this.metadata) { + return this.metadata + } + return (this.metadata = await this.provider.client.getCoinMetadata({ + coinType: this.address + })) + } + + /** + * @returns Token name + */ + async getName(): Promise { + return (await this.getMetadata())?.name ?? '' + } + + /** + * @returns Token symbol + */ + async getSymbol(): Promise { + return (await this.getMetadata())?.symbol ?? '' + } + + /** + * @returns Decimal value of the token + */ + async getDecimals(): Promise { + return (await this.getMetadata())?.decimals ?? 0 + } + + /** + * @param amount Amount of tokens that will be transferred + * @returns Formatted amount + */ + private async toMist(amount: number | string): Promise { + const decimals = await this.getDecimals() + return math.mul(Number(amount), math.pow(10, decimals), decimals) + } + + /** + * @param amount Amount of tokens that will be transferred + * @returns Formatted amount + */ + private async fromMist(amount: number | string): Promise { + const decimals = await this.getDecimals() + return math.div(Number(amount), math.pow(10, decimals), decimals) + } + + /** + * @param owner Wallet address + * @returns Wallet balance as currency of TOKEN + */ + async getBalance(owner: WalletAddress): Promise { + const balance = await this.provider.client.getBalance({ + owner, + coinType: this.address + }) + return await this.fromMist(balance.totalBalance) + } + + /** + * @returns Total supply of the token + */ + async getTotalSupply(): Promise { + const supply = await this.provider.client.getTotalSupply({ + coinType: this.address + }) + return await this.fromMist(supply.value) + } + + /** + * @param _owner Address of owner of the tokens that is being used + * @param _spender Address of the spender that is using the tokens of owner + * @returns Amount of tokens that the spender is allowed to spend + */ + async getAllowance(_owner: WalletAddress, _spender: WalletAddress): Promise { + throw new Error('Method not implemented.') + } + + /** + * transfer() method is the main method for processing transfers for fungible assets (TOKEN, COIN) + * @param sender Sender wallet address + * @param receiver Receiver wallet address + * @param amount Amount of assets that will be transferred + * @returns Transaction signer + */ + async transfer( + sender: WalletAddress, + receiver: WalletAddress, + amount: TransferAmount + ): Promise { + if (amount <= 0) { + throw new Error(ErrorTypeEnum.INVALID_AMOUNT) + } + + const balance = await this.getBalance(sender) + + if (amount > balance) { + throw new Error(ErrorTypeEnum.INSUFFICIENT_BALANCE) + } + + amount = await this.toMist(amount) + + const coins = await this.provider.client.getCoins({ + owner: sender, + coinType: this.address + }) + + const enoughBalanceCoin = coins.data.find((c) => Number(c.balance) >= amount) + + const tx = new Transaction() + + if (enoughBalanceCoin) { + tx.transferObjects( + [tx.splitCoins(tx.object(enoughBalanceCoin.coinObjectId), [amount])], + receiver + ) + } else { + const coinObjectIds = coins.data.map((coin) => coin.coinObjectId) + const primaryCoin = tx.object(coinObjectIds[0]) + + if (coinObjectIds.length > 1) { + tx.mergeCoins( + primaryCoin, + coinObjectIds.slice(1).map((id) => tx.object(id)) + ) + } + + tx.transferObjects([tx.splitCoins(primaryCoin, [amount])], receiver) + } + + return new TransactionSigner(tx) + } + + /** + * @param _spender Address of the spender of transaction + * @param _owner Sender wallet address + * @param _receiver Receiver wallet address + * @param _amount Amount of tokens that will be transferred + * @returns Transaction signer + */ + async transferFrom( + _spender: WalletAddress, + _owner: WalletAddress, + _receiver: WalletAddress, + _amount: TransferAmount + ): Promise { + throw new Error('Method not implemented.') + } + + /** + * Gives permission to the spender to spend owner's tokens + * @param _owner Address of owner of the tokens that will be used + * @param _spender Address of the spender that will use the tokens of owner + * @param _amount Amount of the tokens that will be used + * @returns Transaction signer + */ + async approve( + _owner: WalletAddress, + _spender: WalletAddress, + _amount: TransferAmount + ): Promise { + throw new Error('Method not implemented.') + } +} diff --git a/packages/networks/sui/src/assets/index.ts b/packages/networks/sui/src/assets/index.ts new file mode 100644 index 0000000..737051b --- /dev/null +++ b/packages/networks/sui/src/assets/index.ts @@ -0,0 +1,4 @@ +export * from './NFT' +export * from './Coin' +export * from './Token' +export * from './Contract' diff --git a/packages/networks/sui/src/browser/Wallet.ts b/packages/networks/sui/src/browser/Wallet.ts new file mode 100644 index 0000000..c724ed8 --- /dev/null +++ b/packages/networks/sui/src/browser/Wallet.ts @@ -0,0 +1,202 @@ +import { Provider } from '../services/Provider' +import type { Transaction } from '@mysten/sui/transactions' +import type { TransactionSigner } from '../services/TransactionSigner' +import { + type WalletInterface, + type WalletAdapterInterface, + type WalletPlatformEnum, + type TransactionId, + type SignedMessage, + type WalletAddress, + type ConnectConfig, + type UnknownConfig, + ErrorTypeEnum +} from '@multiplechain/types' + +export interface WalletProvider { + getAddress: () => Promise + signMessage: (message: string) => Promise + sendTransaction: (transaction: Transaction) => Promise + on: (event: string, callback: (data: any) => void) => void +} + +const rejectMap = (error: any, reject: (a: any) => any): any => { + console.error('MultipleChain Sui Wallet Error:', error) + + const errorMessage = String(error.message ?? '') + + if ( + errorMessage.includes('The user rejected the request') || + errorMessage.includes('User rejected the request') || + errorMessage.includes('WALLET.CONNECT_ERROR') || + errorMessage.includes('wallet unknown error') || + errorMessage.includes('User rejection') + ) { + return reject(new Error(ErrorTypeEnum.WALLET_REQUEST_REJECTED)) + } + + return reject(error) +} + +type WalletAdapter = WalletAdapterInterface + +export class Wallet implements WalletInterface { + /** + * WalletAdapter instance + */ + adapter: WalletAdapter + + /** + * Wallet provider is the instance of the wallet connection + */ + walletProvider: WalletProvider + + /** + * Network provider is the instance of the blockchain network connection + */ + networkProvider: Provider + + /** + * @param adapter - WalletAdapter instance + * @param provider - Network provider + */ + constructor(adapter: WalletAdapter, provider?: Provider) { + this.adapter = adapter + this.networkProvider = provider ?? Provider.instance + } + + /** + * @returns wallet id + */ + getId(): string { + return this.adapter.id + } + + /** + * @returns wallet name + */ + getName(): string { + return this.adapter.name + } + + /** + * @returns wallet icon + */ + getIcon(): string { + return this.adapter.icon + } + + /** + * @returns wallet platforms + */ + getPlatforms(): WalletPlatformEnum[] { + return this.adapter.platforms + } + + /** + * @returns wallet download link + */ + getDownloadLink(): string | undefined { + return this.adapter.downloadLink + } + + /** + * @param url - URL to create a deep link + * @param config - Configuration for the deep link + * @returns deep link + */ + createDeepLink(url: string, config?: UnknownConfig): string | null { + if (this.adapter.createDeepLink === undefined) { + return null + } + + return this.adapter.createDeepLink(url, config) + } + + /** + * @param config - Configuration for the connection + * @returns wallet address + */ + async connect(config?: ConnectConfig): Promise { + return await new Promise((resolve, reject) => { + this.adapter + .connect(this.networkProvider, config) + .then(async (provider) => { + this.walletProvider = provider + resolve(await this.getAddress()) + }) + .catch((error) => { + rejectMap(error, (error: any): void => { + if (error.message === ErrorTypeEnum.WALLET_REQUEST_REJECTED) { + reject(new Error(ErrorTypeEnum.WALLET_CONNECT_REJECTED)) + } else { + reject(error) + } + }) + }) + }) + } + + /** + * @returns wallet detected status + */ + async isDetected(): Promise { + return await this.adapter.isDetected() + } + + /** + * @returns wallet connected status + */ + async isConnected(): Promise { + return await this.adapter.isConnected() + } + + /** + * @returns wallet address + */ + async getAddress(): Promise { + return await this.walletProvider.getAddress() + } + + /** + * @param message - Message to sign + * @returns signed message + */ + async signMessage(message: string): Promise { + return await new Promise((resolve, reject) => { + this.walletProvider + .signMessage(message) + .then((signature) => { + resolve(signature) + }) + .catch((error) => { + rejectMap(error, reject) + }) + }) + } + + /** + * @param transactionSigner - Transaction signer + * @returns transaction id + */ + async sendTransaction(transactionSigner: TransactionSigner): Promise { + return await new Promise((resolve, reject) => { + this.walletProvider + .sendTransaction(transactionSigner.getRawData()) + .then((txHash) => { + resolve(txHash) + }) + .catch((error) => { + rejectMap(error, reject) + }) + }) + } + + /** + * @param eventName - Event name + * @param callback - Event callback + */ + on(eventName: string, callback: (...args: any[]) => void): void { + this.walletProvider.on(eventName, callback) + } +} diff --git a/packages/networks/sui/src/browser/adapters/Martian.ts b/packages/networks/sui/src/browser/adapters/Martian.ts new file mode 100644 index 0000000..b987e65 --- /dev/null +++ b/packages/networks/sui/src/browser/adapters/Martian.ts @@ -0,0 +1,113 @@ +import { martian } from './icons' +import { toBase64 } from '@mysten/sui/utils' +import type { WalletProvider } from '../Wallet' +import { WalletAdapter } from '@suiet/wallet-sdk' +import type { Provider } from '../../services/Provider' +import { adapterToProvider, getWalletByName } from './standard' +import type { WalletAdapterInterface } from '@multiplechain/types' +import { ErrorTypeEnum, WalletPlatformEnum } from '@multiplechain/types' +import type { + SuiSignAndExecuteTransactionOutput, + SuiSignTransactionInput +} from '@mysten/wallet-standard' + +declare global { + interface Window { + martian: { + sui: { + signTransactionBlock: (input: { + transactionBlockSerialized: string + options?: { + showEffects?: boolean + showRawEffects?: boolean + } + }) => Promise<{ + transactionBlockBytes: string + signature: string + }> + signAndExecuteTransactionBlock: (input: { + transactionBlockSerialized: string + options?: { + showEffects?: boolean + showRawEffects?: boolean + } + }) => Promise<{ + digest: string + rawEffects: number[] + }> + } + } + } +} + +const wallet = getWalletByName('Martian Sui Wallet') + +interface ExtendedSuiSignTransactionInput extends SuiSignTransactionInput { + transaction: { + serialize: () => string + toJSON: () => Promise + } +} + +const Martian: WalletAdapterInterface = { + icon: martian, + id: 'martian', + name: 'Martian', + platforms: [WalletPlatformEnum.BROWSER], + downloadLink: 'https://martianwallet.xyz/', + isDetected: () => Boolean(window.martian.sui), + isConnected: () => Boolean(wallet?.accounts.length), + disconnect: async () => { + try { + if (wallet) { + await new WalletAdapter(wallet).disconnect() + } + } catch (error) { + console.error('Error disconnecting from Martian wallet:', error) + } + }, + connect: async (provider?: Provider) => { + if (provider === undefined) { + throw new Error(ErrorTypeEnum.PROVIDER_IS_REQUIRED) + } + + if (!wallet || !window.martian.sui) { + throw new Error(ErrorTypeEnum.WALLET_CONNECTION_FAILED) + } + + const martian = window.martian.sui + const adapter = new WalletAdapter(wallet) + + adapter.signAndExecuteTransaction = async ( + input: ExtendedSuiSignTransactionInput + ): Promise => { + const signed = await martian.signTransactionBlock({ + transactionBlockSerialized: input.transaction.serialize() + }) + const res = await provider.client.executeTransactionBlock({ + transactionBlock: signed.transactionBlockBytes, + signature: signed.signature, + options: { + showRawEffects: true + } + }) + return { + digest: res.digest, + signature: signed.signature, + bytes: signed.transactionBlockBytes, + effects: toBase64(new Uint8Array(res.rawEffects ?? [])) + } + } + + return await new Promise((resolve, reject) => { + adapter + .connect({}) + .then(() => { + resolve(adapterToProvider(adapter, provider)) + }) + .catch(reject) + }) + } +} + +export default Martian diff --git a/packages/networks/sui/src/browser/adapters/MetaMask.ts b/packages/networks/sui/src/browser/adapters/MetaMask.ts new file mode 100644 index 0000000..c5da601 --- /dev/null +++ b/packages/networks/sui/src/browser/adapters/MetaMask.ts @@ -0,0 +1,122 @@ +import { metaMask } from './icons' +import type { WalletProvider } from '../Wallet' +import type { Provider } from '../../services/Provider' +import type { Transaction } from '@mysten/sui/transactions' +import type { WalletAdapterInterface } from '@multiplechain/types' +import type { ReadonlyWalletAccount } from '@mysten/wallet-standard' +import { ErrorTypeEnum, WalletPlatformEnum } from '@multiplechain/types' +import { + SNAP_ORIGIN as snapId, + serializeSuiSignMessageInput, + getAccounts as getAccountsBase, + serializeSuiSignAndExecuteTransactionBlockInput +} from '@kunalabs-io/sui-snap-wallet' + +interface WindowEthereum { + isMetaMask?: boolean + on: (event: string, callback: (data: any) => void) => void + request: (payload: { method: string; params?: any[] | object }) => Promise +} + +declare global { + interface Window { + ethereum: WindowEthereum + } +} + +const getAccounts = async (): Promise => { + // @ts-expect-error no worry + return await getAccountsBase(window.ethereum) +} + +const MetaMask: WalletAdapterInterface = { + id: 'metamask', + icon: metaMask, + name: 'MetaMask Snap', + platforms: [WalletPlatformEnum.BROWSER], + downloadLink: 'https://metamask.io/download/', + isDetected: () => { + return Boolean((window?.ethereum as unknown as WindowEthereum)?.isMetaMask) + }, + isConnected: async () => { + return Boolean((await getAccounts()).length) + }, + connect: async (provider?: Provider) => { + return await new Promise((resolve, reject) => { + if (provider === undefined) { + throw new Error(ErrorTypeEnum.PROVIDER_IS_REQUIRED) + } + + const network = provider?.isTestnet() ? 'devnet' : 'mainnet' + const metamaskProvider = window?.ethereum as unknown as WindowEthereum + + try { + const walletProvider: WalletProvider = { + getAddress: async (): Promise => { + return (await getAccounts())[0].address + }, + signMessage: async (message: string): Promise => { + const walletAccount = (await getAccounts())[0] + const serialized = serializeSuiSignMessageInput({ + message: new TextEncoder().encode(message), + account: walletAccount + }) + const res = await metamaskProvider.request({ + method: 'wallet_invokeSnap', + params: { + snapId, + request: { + method: 'signPersonalMessage', + params: JSON.parse(JSON.stringify(serialized)) + } + } + }) + return res.signature + }, + sendTransaction: async (transaction: Transaction): Promise => { + const serialized = serializeSuiSignAndExecuteTransactionBlockInput({ + // @ts-expect-error it will work + transactionBlock: transaction, + account: (await getAccounts())[0], + chain: `sui:${network}` + }) + const res = await metamaskProvider.request({ + method: 'wallet_invokeSnap', + params: { + snapId, + request: { + method: 'signAndExecuteTransactionBlock', + params: JSON.parse(JSON.stringify(serialized)) + } + } + }) + return res.digest + }, + on: (event: string, callback: (data: any) => void) => { + metamaskProvider.on(event, callback) + } + } + + const connect = async (): Promise => { + try { + await metamaskProvider.request({ + method: 'wallet_requestSnaps', + params: { + [snapId]: {} + } + }) + resolve(walletProvider) + } catch (error) { + reject(error) + } + } + + void connect() + } catch (error) { + reject(error) + } + }) + } +} + +export default MetaMask diff --git a/packages/networks/sui/src/browser/adapters/Phantom.ts b/packages/networks/sui/src/browser/adapters/Phantom.ts new file mode 100644 index 0000000..1ecab7e --- /dev/null +++ b/packages/networks/sui/src/browser/adapters/Phantom.ts @@ -0,0 +1,70 @@ +import { phantom } from './icons' +import type { WalletProvider } from '../Wallet' +import { WalletAdapter } from '@suiet/wallet-sdk' +import type { Provider } from '../../services/Provider' +import type { WalletAdapterInterface } from '@multiplechain/types' +import { ErrorTypeEnum, WalletPlatformEnum } from '@multiplechain/types' +import { adapterToProvider, getWalletByName, type BasicAccount } from './standard' + +declare global { + interface Window { + phantom?: { + sui?: { + isPhantom?: boolean + requestAccount: () => Promise + } + } + } +} + +const wallet = getWalletByName('Phantom') + +const Phantom: WalletAdapterInterface = { + icon: phantom, + id: 'phantom', + name: 'Phantom', + downloadLink: 'https://phantom.app/download', + platforms: [WalletPlatformEnum.BROWSER, WalletPlatformEnum.MOBILE], + createDeepLink: (url: string): string => `https://phantom.app/ul/browse/${url}?ref=${url}`, + isDetected: () => Boolean(window.phantom?.sui?.isPhantom), + isConnected: async () => { + if (!window.phantom?.sui) { + return false + } + + return Boolean(await window.phantom?.sui?.requestAccount()) + }, + disconnect: async () => { + try { + if (wallet) { + await new WalletAdapter(wallet).disconnect() + } + } catch (error) { + console.error('Error disconnecting from Phantom wallet:', error) + } + }, + connect: async (provider?: Provider) => { + if (provider === undefined) { + throw new Error(ErrorTypeEnum.PROVIDER_IS_REQUIRED) + } + + if (!wallet || !window.phantom?.sui) { + throw new Error(ErrorTypeEnum.WALLET_CONNECTION_FAILED) + } + + const adapter = new WalletAdapter(wallet) + + const account = await window.phantom?.sui?.requestAccount() + + return await new Promise((resolve, reject) => { + adapter + .connect({}) + .then(() => { + resolve(adapterToProvider(adapter, provider, account as BasicAccount)) + }) + .catch(reject) + }) + } +} + +export default Phantom diff --git a/packages/networks/sui/src/browser/adapters/SuiWallet.ts b/packages/networks/sui/src/browser/adapters/SuiWallet.ts new file mode 100644 index 0000000..73f1fbb --- /dev/null +++ b/packages/networks/sui/src/browser/adapters/SuiWallet.ts @@ -0,0 +1,50 @@ +import { suiWallet } from './icons' +import type { WalletProvider } from '../Wallet' +import { WalletAdapter } from '@suiet/wallet-sdk' +import type { Provider } from '../../services/Provider' +import { adapterToProvider, getWalletByName } from './standard' +import type { WalletAdapterInterface } from '@multiplechain/types' +import { ErrorTypeEnum, WalletPlatformEnum } from '@multiplechain/types' + +const wallet = getWalletByName('Sui Wallet') + +const SuiWallet: WalletAdapterInterface = { + id: 'suiwallet', + name: 'Sui Wallet', + icon: suiWallet, + downloadLink: 'https://suiwallet.com/', + platforms: [WalletPlatformEnum.BROWSER, WalletPlatformEnum.MOBILE], + isDetected: () => Boolean(wallet), + isConnected: () => Boolean(wallet?.accounts.length), + disconnect: async () => { + try { + if (wallet) { + await new WalletAdapter(wallet).disconnect() + } + } catch (error) { + console.error('Error disconnecting from Sui Wallet:', error) + } + }, + connect: async (provider?: Provider) => { + if (provider === undefined) { + throw new Error(ErrorTypeEnum.PROVIDER_IS_REQUIRED) + } + + if (!wallet) { + throw new Error(ErrorTypeEnum.WALLET_CONNECTION_FAILED) + } + + const adapter = new WalletAdapter(wallet) + + return await new Promise((resolve, reject) => { + adapter + .connect({}) + .then(() => { + resolve(adapterToProvider(adapter, provider)) + }) + .catch(reject) + }) + } +} + +export default SuiWallet diff --git a/packages/networks/sui/src/browser/adapters/Suiet.ts b/packages/networks/sui/src/browser/adapters/Suiet.ts new file mode 100644 index 0000000..e8895fa --- /dev/null +++ b/packages/networks/sui/src/browser/adapters/Suiet.ts @@ -0,0 +1,50 @@ +import { suiet } from './icons' +import type { WalletProvider } from '../Wallet' +import { WalletAdapter } from '@suiet/wallet-sdk' +import type { Provider } from '../../services/Provider' +import { adapterToProvider, getWalletByName } from './standard' +import type { WalletAdapterInterface } from '@multiplechain/types' +import { ErrorTypeEnum, WalletPlatformEnum } from '@multiplechain/types' + +const wallet = getWalletByName('Suiet') + +const Suiet: WalletAdapterInterface = { + icon: suiet, + id: 'suiet', + name: 'Suiet', + platforms: [WalletPlatformEnum.BROWSER], + downloadLink: 'https://suiet.app/install', + isDetected: () => Boolean(wallet), + isConnected: () => Boolean(wallet?.accounts.length), + disconnect: async () => { + try { + if (wallet) { + await new WalletAdapter(wallet).disconnect() + } + } catch (error) { + console.error('Error disconnecting from Suiet wallet:', error) + } + }, + connect: async (provider?: Provider) => { + if (provider === undefined) { + throw new Error(ErrorTypeEnum.PROVIDER_IS_REQUIRED) + } + + if (!wallet) { + throw new Error(ErrorTypeEnum.WALLET_CONNECTION_FAILED) + } + + const adapter = new WalletAdapter(wallet) + + return await new Promise((resolve, reject) => { + adapter + .connect({}) + .then(() => { + resolve(adapterToProvider(adapter, provider)) + }) + .catch(reject) + }) + } +} + +export default Suiet diff --git a/packages/networks/sui/src/browser/adapters/SurfWallet.ts b/packages/networks/sui/src/browser/adapters/SurfWallet.ts new file mode 100644 index 0000000..7da9452 --- /dev/null +++ b/packages/networks/sui/src/browser/adapters/SurfWallet.ts @@ -0,0 +1,50 @@ +import { surfWallet } from './icons' +import type { WalletProvider } from '../Wallet' +import { WalletAdapter } from '@suiet/wallet-sdk' +import type { Provider } from '../../services/Provider' +import { adapterToProvider, getWalletByName } from './standard' +import type { WalletAdapterInterface } from '@multiplechain/types' +import { ErrorTypeEnum, WalletPlatformEnum } from '@multiplechain/types' + +const wallet = getWalletByName('Surf Wallet') + +const SurfWallet: WalletAdapterInterface = { + icon: surfWallet, + id: 'surfwallet', + name: 'Surf Wallet', + platforms: [WalletPlatformEnum.BROWSER, WalletPlatformEnum.MOBILE], + downloadLink: 'https://surf.tech/', + isDetected: () => Boolean(wallet), + isConnected: () => Boolean(wallet?.accounts.length), + disconnect: async () => { + try { + if (wallet) { + await new WalletAdapter(wallet).disconnect() + } + } catch (error) { + console.error('Error disconnecting from Surf Wallet:', error) + } + }, + connect: async (provider?: Provider) => { + if (provider === undefined) { + throw new Error(ErrorTypeEnum.PROVIDER_IS_REQUIRED) + } + + if (!wallet) { + throw new Error(ErrorTypeEnum.WALLET_CONNECTION_FAILED) + } + + const adapter = new WalletAdapter(wallet) + + return await new Promise((resolve, reject) => { + adapter + .connect({}) + .then(() => { + resolve(adapterToProvider(adapter, provider)) + }) + .catch(reject) + }) + } +} + +export default SurfWallet diff --git a/packages/networks/sui/src/browser/adapters/icons.ts b/packages/networks/sui/src/browser/adapters/icons.ts new file mode 100644 index 0000000..18e5c07 --- /dev/null +++ b/packages/networks/sui/src/browser/adapters/icons.ts @@ -0,0 +1,17 @@ +export const metaMask = + 'data:image/png;base64,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' + +export const suiet = + 'data:image/png;base64,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' + +export const phantom = + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTA4IiBoZWlnaHQ9IjEwOCIgdmlld0JveD0iMCAwIDEwOCAxMDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMDgiIGhlaWdodD0iMTA4IiByeD0iMjYiIGZpbGw9IiNBQjlGRjIiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00Ni41MjY3IDY5LjkyMjlDNDIuMDA1NCA3Ni44NTA5IDM0LjQyOTIgODUuNjE4MiAyNC4zNDggODUuNjE4MkMxOS41ODI0IDg1LjYxODIgMTUgODMuNjU2MyAxNSA3NS4xMzQyQzE1IDUzLjQzMDUgNDQuNjMyNiAxOS44MzI3IDcyLjEyNjggMTkuODMyN0M4Ny43NjggMTkuODMyNyA5NCAzMC42ODQ2IDk0IDQzLjAwNzlDOTQgNTguODI1OCA4My43MzU1IDc2LjkxMjIgNzMuNTMyMSA3Ni45MTIyQzcwLjI5MzkgNzYuOTEyMiA2OC43MDUzIDc1LjEzNDIgNjguNzA1MyA3Mi4zMTRDNjguNzA1MyA3MS41NzgzIDY4LjgyNzUgNzAuNzgxMiA2OS4wNzE5IDY5LjkyMjlDNjUuNTg5MyA3NS44Njk5IDU4Ljg2ODUgODEuMzg3OCA1Mi41NzU0IDgxLjM4NzhDNDcuOTkzIDgxLjM4NzggNDUuNjcxMyA3OC41MDYzIDQ1LjY3MTMgNzQuNDU5OEM0NS42NzEzIDcyLjk4ODQgNDUuOTc2OCA3MS40NTU2IDQ2LjUyNjcgNjkuOTIyOVpNODMuNjc2MSA0Mi41Nzk0QzgzLjY3NjEgNDYuMTcwNCA4MS41NTc1IDQ3Ljk2NTggNzkuMTg3NSA0Ny45NjU4Qzc2Ljc4MTYgNDcuOTY1OCA3NC42OTg5IDQ2LjE3MDQgNzQuNjk4OSA0Mi41Nzk0Qzc0LjY5ODkgMzguOTg4NSA3Ni43ODE2IDM3LjE5MzEgNzkuMTg3NSAzNy4xOTMxQzgxLjU1NzUgMzcuMTkzMSA4My42NzYxIDM4Ljk4ODUgODMuNjc2MSA0Mi41Nzk0Wk03MC4yMTAzIDQyLjU3OTVDNzAuMjEwMyA0Ni4xNzA0IDY4LjA5MTYgNDcuOTY1OCA2NS43MjE2IDQ3Ljk2NThDNjMuMzE1NyA0Ny45NjU4IDYxLjIzMyA0Ni4xNzA0IDYxLjIzMyA0Mi41Nzk1QzYxLjIzMyAzOC45ODg1IDYzLjMxNTcgMzcuMTkzMSA2NS43MjE2IDM3LjE5MzFDNjguMDkxNiAzNy4xOTMxIDcwLjIxMDMgMzguOTg4NSA3MC4yMTAzIDQyLjU3OTVaIiBmaWxsPSIjRkZGREY4Ii8+Cjwvc3ZnPgo=' + +export const suiWallet = + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxyZWN0IHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCIgZmlsbD0iIzRDQTNGRiIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xOC44MzI3IDEyLjM0MTNWMTIuMzQyMkMxOS42NDgyIDEzLjM2NTMgMjAuMTM2IDE0LjY2MTMgMjAuMTM2IDE2LjA3MDVDMjAuMTM2IDE3LjQ3OTggMTkuNjMzNyAxOC44MTQzIDE4Ljc5NTcgMTkuODQ0M0wxOC43MjM1IDE5LjkzM0wxOC43MDQ1IDE5LjgyMDNDMTguNjg4MiAxOS43MjQ1IDE4LjY2OSAxOS42Mjc1IDE4LjY0NyAxOS41M0MxOC4yMjc3IDE3LjY4NzUgMTYuODYxMiAxNi4xMDc1IDE0LjYxMjUgMTQuODI4MkMxMy4wOTQgMTMuOTY2OCAxMi4yMjQ3IDEyLjkyOTIgMTEuOTk2NSAxMS43NTA4QzExLjg0OSAxMC45ODg1IDExLjk1ODcgMTAuMjIzIDEyLjE3MDUgOS41NjcyNUMxMi4zODIyIDguOTExNzUgMTIuNjk3MiA4LjM2MjUgMTIuOTY0NyA4LjAzMkwxMy44Mzk1IDYuOTYyMjVDMTMuOTkzIDYuNzc0NzUgMTQuMjggNi43NzQ3NSAxNC40MzM1IDYuOTYyMjVMMTguODMzIDEyLjM0MTVMMTguODMyNyAxMi4zNDEzWk0yMC4yMTY1IDExLjI3MjVWMTEuMjcyTDE0LjM1MyA0LjEwMjc1QzE0LjI0MSAzLjk2NTc1IDE0LjAzMTUgMy45NjU3NSAxMy45MTk1IDQuMTAyNzVMOC4wNTYgMTEuMjcyM1YxMS4yNzI4TDguMDM3IDExLjI5NjVDNi45NTgyNSAxMi42MzUzIDYuMzEyNSAxNC4zMzY4IDYuMzEyNSAxNi4xODlDNi4zMTI1IDIwLjUwMjggOS44MTUyNSAyNCAxNC4xMzYzIDI0QzE4LjQ1NzIgMjQgMjEuOTYgMjAuNTAyOCAyMS45NiAxNi4xODlDMjEuOTYgMTQuMzM2OCAyMS4zMTQyIDEyLjYzNTMgMjAuMjM1MiAxMS4yOTYzTDIwLjIxNiAxMS4yNzI1SDIwLjIxNjVaTTkuNDU5MjUgMTIuMzE4TDkuOTgzNzUgMTEuNjc2NUw5Ljk5OTUgMTEuNzk1QzEwLjAxMiAxMS44ODg3IDEwLjAyNzIgMTEuOTgzIDEwLjA0NTIgMTIuMDc3OEMxMC4zODQ1IDEzLjg1ODIgMTEuNTk2NyAxNS4zNDI4IDEzLjYyMzUgMTYuNDkyNUMxNS4zODUyIDE3LjQ5NSAxNi40MTEgMTguNjQ4IDE2LjcwNjUgMTkuOTEyNUMxNi44Mjk4IDIwLjQ0MDMgMTYuODUxNyAyMC45NTk1IDE2Ljc5ODUgMjEuNDEzNUwxNi43OTUyIDIxLjQ0MTVMMTYuNzY5NyAyMS40NTRDMTUuOTc0NyAyMS44NDI1IDE1LjA4MDcgMjIuMDYwNSAxNC4xMzYgMjIuMDYwNUMxMC44MjI1IDIyLjA2MDUgOC4xMzYyNSAxOS4zNzg4IDguMTM2MjUgMTYuMDcwNUM4LjEzNjI1IDE0LjY1MDMgOC42MzE1IDEzLjM0NSA5LjQ1OSAxMi4zMTgzTDkuNDU5MjUgMTIuMzE4WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg==' + +export const surfWallet = + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik00NzAuMDc3IDM5OS45MDZDNDU5LjIxNCA0MDcuOTM1IDQ0Ny4yNDggNDEzLjc2IDQzNC45NjggNDE0LjcwNUMzODguMzY2IDQxOC42NDEgMzI4LjUzNyAzNzIuODI2IDI5MC41OTQgMzY3Ljk0NUMyNTIuNjUxIDM2My4wNjUgMjMxLjM5NyAzODguNzI4IDIyMi4yNjYgNDAzLjY4NEMyMTYuNTk4IDQxMi44MTYgMjE1LjU2NSA0MjYuMTcxIDIxNS44OCA0MzYuODc3QzIxNi4xOTUgNDQxLjEyOCAyMTYuNDQgNDQ1LjkyNyAyMTcuODU3IDQ1Mi4xNzZDMjIwLjM0OSA0NjMuMTcxIDI0Ny45MjggNTA2LjY1MSAzMTEuNTM0IDUwMS4xNEMzODAuOTY1IDQ5NS4xNTcgNDIxLjI3IDQ1Ny44NDQgNDYyLjM2MiA0MDkuMDM3TDQ2Ny41NTggNDAyLjc0QzQ2OC4zNDUgNDAxLjc5NSA0NjkuMjkgNDAwLjY5MyA0NzAuMDc3IDM5OS43NDhWMzk5LjkwNloiIGZpbGw9IiM1OEM1RjMiLz4KPHBhdGggZD0iTTI1NC4zNiAzMjcuMDA5QzI2NC43NTEgMzIwLjcxMSAyNzUuNzcyIDMxNi40NjEgMjg2Ljc5MiAzMTYuNDYxQzMyOC44MjkgMzE2LjQ2MSAzNzguNTgxIDM2MS45NjEgNDEyLjExNSAzNjkuMjAzQzQ0NS42NSAzNzYuNDQ1IDQ2OC4zMjIgMzU0LjQwNyA0NzUuODc5IDM0Mi40MzhDNDgzLjQzNiAzMzAuNDcgNDg0LjM1MSAzMTkuMDgzIDQ4My45MDggMzEwLjk1QzQ4My40NjUgMzAyLjgxNyA0ODMuNzUgMzAzLjIzNiA0ODMuNDM2IDI5OS40NTdDNDgwLjYwMiAyOTIuMDU3IDQ2MC43NjUgMjQ4LjYwNCA0MDMuNjE0IDI0OC42MDRDMzQxLjI2OCAyNDguNjA0IDMwMi4zOCAyNzguODMyIDI2MS45MTggMzE5LjI5NEwyNTYuNzIyIDMyNC40OUMyNTUuOTM1IDMyNS4yNzcgMjU0Ljk4OSAzMjYuMjIyIDI1NC4yMDIgMzI3LjAwOUgyNTQuMzZaIiBmaWxsPSIjOURFMkZGIi8+CjxwYXRoIGQ9Ik0zMyAyOTUuNTI1TDMzLjAwMzkgMjk4LjQzNUMzMy4wMDM5IDM1NS45IDc5LjYwMyA0MDAuNjk1IDEzNi45MTEgNDAwLjY5NUMxNjEuNDcyIDQwMC42OTUgMTgxLjkxOSAzOTMuOTIgMTk5LjcxIDM3OS43NUwyMDAuNzYgMzc4Ljc2N0MyMDAuNzYgMzc4Ljc2NyAyMDEuNDE3IDM3OC4yMjYgMjAxLjkzNCAzNzcuNzA5QzIwMi4wOTIgMzc3LjU1MSAyMDIuNDA2IDM3Ny4yMzYgMjAyLjU2NCAzNzcuMDc5QzIwMi43MjEgMzc2LjkyMSAyMDMuMDM2IDM3Ni42MDcgMjAzLjE5MyAzNzYuNDQ5QzIwNS4yNCAzNzQuNTYgMjA4Ljg2MSAzNzEuMDk2IDIxNC4yMTQgMzY2LjA1OEMyMjMuMDMxIDM1Ny43MTQgMjM2LjI1NiAzNDQuODA0IDI1NC4wNDcgMzI3LjE3QzI1NC44MzQgMzI2LjM4MyAyNTUuNzc5IDMyNS40MzggMjU2LjU2NiAzMjQuNjUxTDI2MS43NjIgMzE5LjQ1NkMzMDIuMDY2IDI3OS4xNTEgMzQwLjk1NCAyNDguNzY1IDQwMy40NTggMjQ4Ljc2NUM0NjAuNjA5IDI0OC43NjUgNDgwLjQ0NyAyOTIuMjE4IDQ4My4yODEgMjk5LjYxOEM0NzcuNjEzIDIwMC41ODggNDA4LjE4MSAxMTguNzE5IDMxNS40NDggOTQuMzE1N0MzMTUuNDQ4IDk0LjMxNTcgMzEzLjcxNyA5My44NDM0IDMxMS44MjggOTMuMzcxMUMzMDIuMjI0IDkwLjUzNzEgMjc2LjA4OCA4MS40MDU0IDI3Ni4wODggNjYuMTMzN1YxNy45NTY5QzI3Ni4wODggMTcuOTU2OSAyNzguMzM5IDUuMTA2MjggMjY0LjI4IDE0LjMzNThDMjI4LjA2OSAzOC4xMDk0IDE5Ny4yMTEgODkuOTA3NCAxNjkuMTg2IDEwNS4xNzlDMTY5LjE4NiAxMDUuMTc5IDE2OC44NzEgMTA1LjMzNiAxNjguNzE0IDEwNS40OTRDMTAwLjIyNyAxMzQuNzc4IDQ4LjczNzUgMTk1LjE4MiAzNS4xOTc3IDI3MC41OTZDMzMuNzgwNyAyNzguMzEgMzMuNDc2MiAyODIuMjY5IDMzLjAwMzkgMjkwLjE0MUwzMyAyOTUuNTI1WiIgZmlsbD0iIzU4QzVGMyIvPgo8cGF0aCBkPSJNMjU1LjQ5NSAyNzEuMzQ5QzI1NS40OTUgMjcxLjM0OSAyMzMuODIzIDI4Ny45MDUgMjExLjcyMyAzMDYuNTg2QzE5NS4xNzMgMzIwLjU3NSAxNzguOTYxIDMzNS45MzkgMTY4LjI3MSAzNDUuNDM0QzE2MS41MDMgMzUxLjQ0NCAxNTIuODIyIDM0OS4xNzYgMTUwLjQ3NCAzMzguMTIxQzE0Ni44NTMgMzI3LjEgMTUzLjQ3MSAyODUuNzY0IDE4NC4xNzIgMjY1Ljc2OUMyMTQuODczIDI0NS43NzQgMjQ2LjIwNCAyNDUuNzc0IDI1Ny42OTcgMjUzLjMzMUMyNjcuNDE1IDI1OS43MjEgMjYzLjc0MyAyNjQuMjAzIDI2Mi4xMDYgMjY1Ljc2OUMyNjAuNDY5IDI2Ny4zMzUgMjU1LjQ5NSAyNzEuMzQ5IDI1NS40OTUgMjcxLjM0OVoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=' + +export const martian = + 'data:image/png;base64,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' diff --git a/packages/networks/sui/src/browser/adapters/index.ts b/packages/networks/sui/src/browser/adapters/index.ts new file mode 100644 index 0000000..2bfccc0 --- /dev/null +++ b/packages/networks/sui/src/browser/adapters/index.ts @@ -0,0 +1,6 @@ +export { default as Suiet } from './Suiet' +export { default as Phantom } from './Phantom' +export { default as Martian } from './Martian' +export { default as MetaMask } from './MetaMask' +export { default as SuiWallet } from './SuiWallet' +export { default as SurfWallet } from './SurfWallet' diff --git a/packages/networks/sui/src/browser/adapters/standard.ts b/packages/networks/sui/src/browser/adapters/standard.ts new file mode 100644 index 0000000..cacef72 --- /dev/null +++ b/packages/networks/sui/src/browser/adapters/standard.ts @@ -0,0 +1,45 @@ +import type { WalletProvider } from '../Wallet' +import type { WalletAdapter } from '@suiet/wallet-sdk' +import type { Provider } from '../../services/Provider' +import type { Transaction } from '@mysten/sui/transactions' +import { getWallets, type StandardEventsNames, type Wallet } from '@mysten/wallet-standard' + +export interface BasicAccount { + address: string + publicKey: Uint8Array +} + +export const getWalletByName = (name: string): Wallet | undefined => { + return Object.values(getWallets().get()).find((adapter) => adapter.name === name) +} + +export const adapterToProvider = ( + adapter: WalletAdapter, + provider: Provider, + account?: BasicAccount +): WalletProvider => { + const network = provider?.isTestnet() ? 'devnet' : 'mainnet' + return { + getAddress: async (): Promise => { + return account ? account.address : adapter.accounts[0].address + }, + signMessage: async (message: string): Promise => { + const res = await adapter.signMessage({ + message: new TextEncoder().encode(message), + account: adapter.accounts[0] + }) + return res.signature + }, + sendTransaction: async (transaction: Transaction): Promise => { + const res = await adapter.signAndExecuteTransaction({ + transaction, + chain: `sui:${network}`, + account: adapter.accounts[0] + }) + return res.digest + }, + on: (event: string, callback: (data: any) => void) => { + adapter.on(event as StandardEventsNames, callback) + } + } +} diff --git a/packages/networks/sui/src/browser/index.ts b/packages/networks/sui/src/browser/index.ts new file mode 100644 index 0000000..28622c8 --- /dev/null +++ b/packages/networks/sui/src/browser/index.ts @@ -0,0 +1,28 @@ +import { Wallet } from './Wallet' +import * as adapterList from './adapters/index' +import type { Provider } from '../services/Provider' +import type { + WalletAdapterListType, + WalletAdapterInterface, + RegisterWalletAdapterType +} from '@multiplechain/types' + +const adapters: WalletAdapterListType = {} + +const registerAdapter: RegisterWalletAdapterType = ( + adapter: WalletAdapterInterface +): void => { + if (Object.values(adapters).find((a) => a.id === adapter.id) !== undefined) { + throw new Error(`Adapter with id ${adapter.id} already exists`) + } + + adapters[adapter.id] = adapter +} + +export * from '../index' + +export const browser = { + Wallet, + registerAdapter, + adapters: Object.assign(adapters, adapterList) +} diff --git a/packages/networks/sui/src/index.ts b/packages/networks/sui/src/index.ts new file mode 100644 index 0000000..612a2b2 --- /dev/null +++ b/packages/networks/sui/src/index.ts @@ -0,0 +1,8 @@ +export * from './services/Provider' + +export * as assets from './assets/index' +export * as models from './models/index' +export * as services from './services/index' + +export * as utils from '@multiplechain/utils' +export * as types from '@multiplechain/types' diff --git a/packages/networks/sui/src/models/CoinTransaction.ts b/packages/networks/sui/src/models/CoinTransaction.ts new file mode 100644 index 0000000..2f5e593 --- /dev/null +++ b/packages/networks/sui/src/models/CoinTransaction.ts @@ -0,0 +1,72 @@ +import { Transaction } from './Transaction' +import { TransactionStatusEnum } from '@multiplechain/types' +import { + AssetDirectionEnum, + type CoinTransactionInterface, + type TransferAmount, + type WalletAddress +} from '@multiplechain/types' +import { fromMist } from '../utils' + +export class CoinTransaction extends Transaction implements CoinTransactionInterface { + /** + * @returns Wallet address of the receiver of transaction + */ + async getReceiver(): Promise { + const data = await this.getData() + if (data === null) { + return '' + } + const ixs = await this.getInputs('pure', 'address') + return ixs?.[0].value as string + } + + /** + * @returns Wallet address of the sender of transaction + */ + async getSender(): Promise { + return await this.getSigner() + } + + /** + * @returns Amount of coin that will be transferred + */ + async getAmount(): Promise { + const ixs = await this.getInputs('pure', 'u64') + return fromMist((ixs?.[0].value as number) ?? 0) + } + + /** + * @param direction - Direction of the transaction (asset) + * @param address - Wallet address of the receiver or sender of the transaction, dependant on direction + * @param amount Amount of assets that will be transferred + * @returns Status of the transaction + */ + async verifyTransfer( + direction: AssetDirectionEnum, + address: WalletAddress, + amount: TransferAmount + ): Promise { + const status = await this.getStatus() + + if (status === TransactionStatusEnum.PENDING) { + return TransactionStatusEnum.PENDING + } + + if ((await this.getAmount()) !== amount) { + return TransactionStatusEnum.FAILED + } + + if (direction === AssetDirectionEnum.INCOMING) { + if ((await this.getReceiver()).toLowerCase() !== address.toLowerCase()) { + return TransactionStatusEnum.FAILED + } + } else { + if ((await this.getSender()).toLowerCase() !== address.toLowerCase()) { + return TransactionStatusEnum.FAILED + } + } + + return TransactionStatusEnum.CONFIRMED + } +} diff --git a/packages/networks/sui/src/models/ContractTransaction.ts b/packages/networks/sui/src/models/ContractTransaction.ts new file mode 100644 index 0000000..453f643 --- /dev/null +++ b/packages/networks/sui/src/models/ContractTransaction.ts @@ -0,0 +1,24 @@ +import { Transaction } from './Transaction' +import { SUI_TYPE_ARG } from '@mysten/sui/utils' +import type { ContractAddress, ContractTransactionInterface } from '@multiplechain/types' + +export class ContractTransaction extends Transaction implements ContractTransactionInterface { + /** + * @returns Contract address of the transaction + */ + async getAddress(): Promise { + const data = await this.getData() + if (data === null) { + return '' + } + for (const change of data.objectChanges ?? []) { + if (change.type === 'published' || change.objectType.includes(SUI_TYPE_ARG)) { + continue + } + const coinMatch = change.objectType.match(/0x2::coin::Coin<(.+)>/) + return coinMatch?.[1] ? coinMatch[1] : change.objectType + } + + return '' + } +} diff --git a/packages/networks/sui/src/models/NftTransaction.ts b/packages/networks/sui/src/models/NftTransaction.ts new file mode 100644 index 0000000..786b2fa --- /dev/null +++ b/packages/networks/sui/src/models/NftTransaction.ts @@ -0,0 +1,75 @@ +import { ContractTransaction } from './ContractTransaction' +import { TransactionStatusEnum } from '@multiplechain/types' +import { + type NftTransactionInterface, + AssetDirectionEnum, + type WalletAddress, + type NftId +} from '@multiplechain/types' + +export class NftTransaction extends ContractTransaction implements NftTransactionInterface { + /** + * @returns Receiver wallet address + */ + async getReceiver(): Promise { + const data = await this.getData() + if (data === null) { + return '' + } + const ixs = await this.getInputs('pure', 'address') + return (ixs?.[0].value ?? '') as string + } + + /** + * @returns Wallet address of the sender of transaction + */ + async getSender(): Promise { + return await this.getSigner() + } + + /** + * @returns NFT ID + */ + async getNftId(): Promise { + const data = await this.getData() + if (data === null) { + return '' + } + const ix = await this.getInputs('object', 'immOrOwnedObject') + return ix?.[0].objectId ?? '' + } + + /** + * @param direction - Direction of the transaction (nft) + * @param address - Wallet address of the receiver or sender of the transaction, dependant on direction + * @param nftId ID of the NFT that will be transferred + * @returns Status of the transaction + */ + async verifyTransfer( + direction: AssetDirectionEnum, + address: WalletAddress, + nftId: NftId + ): Promise { + const status = await this.getStatus() + + if (status === TransactionStatusEnum.PENDING) { + return TransactionStatusEnum.PENDING + } + + if ((await this.getNftId()) !== nftId) { + return TransactionStatusEnum.FAILED + } + + if (direction === AssetDirectionEnum.INCOMING) { + if ((await this.getReceiver()).toLowerCase() !== address.toLowerCase()) { + return TransactionStatusEnum.FAILED + } + } else { + if ((await this.getSender()).toLowerCase() !== address.toLowerCase()) { + return TransactionStatusEnum.FAILED + } + } + + return TransactionStatusEnum.CONFIRMED + } +} diff --git a/packages/networks/sui/src/models/TokenTransaction.ts b/packages/networks/sui/src/models/TokenTransaction.ts new file mode 100644 index 0000000..d79a1a9 --- /dev/null +++ b/packages/networks/sui/src/models/TokenTransaction.ts @@ -0,0 +1,76 @@ +import { ContractTransaction } from './ContractTransaction' +import { TransactionStatusEnum } from '@multiplechain/types' +import { + AssetDirectionEnum, + type TokenTransactionInterface, + type TransferAmount, + type WalletAddress +} from '@multiplechain/types' +import { math } from '../utils' +import { Token } from '../assets' + +export class TokenTransaction extends ContractTransaction implements TokenTransactionInterface { + /** + * @returns Wallet address of the receiver of transaction + */ + async getReceiver(): Promise { + const data = await this.getData() + if (data === null) { + return '' + } + const ixs = await this.getInputs('pure', 'address') + return (ixs?.[0].value ?? '') as string + } + + /** + * @returns Wallet address of the sender of transaction + */ + async getSender(): Promise { + return await this.getSigner() + } + + /** + * @returns Amount of tokens that will be transferred + */ + async getAmount(): Promise { + const address = await this.getAddress() + const ixs = await this.getInputs('pure', 'u64') + const amount = (ixs?.[0].value as number) ?? 0 + const decimals = await new Token(address).getDecimals() + return math.div(amount, math.pow(10, decimals), decimals) + } + + /** + * @param direction - Direction of the transaction (token) + * @param address - Wallet address of the owner or spender of the transaction, dependant on direction + * @param amount Amount of tokens that will be approved + * @returns Status of the transaction + */ + async verifyTransfer( + direction: AssetDirectionEnum, + address: WalletAddress, + amount: TransferAmount + ): Promise { + const status = await this.getStatus() + + if (status === TransactionStatusEnum.PENDING) { + return TransactionStatusEnum.PENDING + } + + if ((await this.getAmount()) !== amount) { + return TransactionStatusEnum.FAILED + } + + if (direction === AssetDirectionEnum.INCOMING) { + if ((await this.getReceiver()).toLowerCase() !== address.toLowerCase()) { + return TransactionStatusEnum.FAILED + } + } else { + if ((await this.getSender()).toLowerCase() !== address.toLowerCase()) { + return TransactionStatusEnum.FAILED + } + } + + return TransactionStatusEnum.CONFIRMED + } +} diff --git a/packages/networks/sui/src/models/Transaction.ts b/packages/networks/sui/src/models/Transaction.ts new file mode 100644 index 0000000..da9e9cd --- /dev/null +++ b/packages/networks/sui/src/models/Transaction.ts @@ -0,0 +1,245 @@ +import { fromMist, math } from '../utils' +import { Provider } from '../services/Provider' +import { ErrorTypeEnum, TransactionStatusEnum } from '@multiplechain/types' +import type { + SuiTransactionBlockResponse, + SuiTransactionBlockKind, + SuiCallArg +} from '@mysten/sui/client' +import { + TransactionTypeEnum, + type BlockConfirmationCount, + type BlockNumber, + type BlockTimestamp, + type TransactionFee, + type TransactionId, + type TransactionInterface, + type WalletAddress +} from '@multiplechain/types' + +type SuiCallArgTypes = SuiCallArg extends { type: infer K } ? K : never +type TransactionKinds = SuiTransactionBlockKind extends { kind: infer K } ? K : never +type SuiTransactionBlockKindMap = { + [K in SuiTransactionBlockKind as K['kind']]: K +} +type SuiCallArgMap = { + [K in SuiCallArg as K['type']]: K +} + +// custom tx data for each blockchain +type TxData = SuiTransactionBlockResponse + +export class Transaction implements TransactionInterface { + /** + * Each transaction has its own unique ID defined by the user + */ + id: TransactionId + + /** + * Transaction data + */ + data: TxData | null = null + + /** + * Blockchain network provider + */ + provider: Provider + + /** + * @param id Transaction id + * @param provider Blockchain network provider + */ + constructor(id: TransactionId, provider?: Provider) { + this.id = id + this.provider = provider ?? Provider.instance + } + + /** + * @returns Transaction data + */ + async getData(): Promise { + if (this.data) { + return this.data + } + try { + const response = await this.provider.client.getTransactionBlock({ + digest: this.id, + options: { + showInput: true, + showEffects: true, + showEvents: true, + showRawInput: true, + showBalanceChanges: true, + showObjectChanges: true + } + }) + if (response.transaction === null) { + return null + } + return (this.data = response) + } catch (error) { + console.error('MC SUI TX getData', error) + if (error instanceof Error && String(error.message).includes('timeout')) { + throw new Error(ErrorTypeEnum.RPC_TIMEOUT) + } + throw new Error(ErrorTypeEnum.RPC_REQUEST_ERROR) + } + } + + /** + * @param ms - Milliseconds to wait for the transaction to be confirmed. Default is 4000ms + * @returns Status of the transaction + */ + async wait(ms: number = 4000): Promise { + return await new Promise((resolve, reject) => { + const check = async (): Promise => { + try { + await this.provider.client.waitForTransaction({ digest: this.id }) + const status = await this.getStatus() + if (status !== TransactionStatusEnum.PENDING) { + resolve(status) + return + } + setTimeout(check, ms) + } catch (error) { + console.error('MC SUI TX wait', error) + reject(TransactionStatusEnum.FAILED) + } + } + void check() + }) + } + + /** + * @returns Transaction ID + */ + getId(): TransactionId { + return this.id + } + + /** + * @returns Type of the transaction + */ + async getType(): Promise { + const data = await this.getData() + + if (data === null) { + return TransactionTypeEnum.GENERAL + } + + switch (data.objectChanges?.length) { + case 2: + return data.balanceChanges?.length === 1 + ? TransactionTypeEnum.NFT + : TransactionTypeEnum.COIN + case 3: + return TransactionTypeEnum.TOKEN + default: + return TransactionTypeEnum.CONTRACT + } + } + + /** + * @returns Transaction URL + */ + getUrl(): string { + let explorerUrl = this.provider.node.explorerUrl + explorerUrl += explorerUrl.endsWith('/') ? '' : '/' + explorerUrl += 'tx/' + this.id + return explorerUrl + } + + /** + * @returns Wallet address of the sender of transaction + */ + async getSigner(): Promise { + return (await this.getData())?.transaction?.data.sender ?? '' + } + + /** + * @returns Transaction fee + */ + async getFee(): Promise { + const data = await this.getData() + if (data === null) { + return 0 + } + const storageCost = fromMist(data.effects?.gasUsed.storageCost ?? 0) + const storageRebate = fromMist(data.effects?.gasUsed.storageRebate ?? 0) + const computationCost = fromMist(data.effects?.gasUsed.computationCost ?? 0) + return math.sub(math.add(storageCost, computationCost, 9), storageRebate, 9) + } + + /** + * @returns Block number that transaction + */ + async getBlockNumber(): Promise { + const data = await this.getData() + if (data === null) { + return 0 + } + return Number(data.checkpoint) + } + + /** + * @returns Block timestamp that transaction + */ + async getBlockTimestamp(): Promise { + const data = await this.getData() + if (data === null) { + return 0 + } + return Number(data.timestampMs) + } + + /** + * @returns Confirmation count of the block + */ + async getBlockConfirmationCount(): Promise { + const blockNumber = await this.getBlockNumber() + const blockCount = + (await this.provider.client.getLatestCheckpointSequenceNumber()) as any as number + const confirmations = blockCount - blockNumber + return confirmations < 0 ? 0 : confirmations + } + + /** + * @returns Status of the transaction + */ + async getStatus(): Promise { + const data = await this.getData() + if (data?.effects?.status.status === 'success') { + return TransactionStatusEnum.CONFIRMED + } else if (data?.effects?.status.status === 'failure') { + return TransactionStatusEnum.FAILED + } else { + return TransactionStatusEnum.PENDING + } + } + + protected async getTransaction( + _kid: T + ): Promise { + const tx = (await this.getData())?.transaction?.data?.transaction + if (tx) { + return tx as SuiTransactionBlockKindMap[T] + } + return undefined + } + + protected async getInputs( + _type: T, + vType?: string | null + ): Promise | undefined> { + const tx = await this.getTransaction('ProgrammableTransaction') + if (tx) { + return tx.inputs.filter((input) => { + if (vType && input.type === 'pure') { + return input.valueType === vType + } + return input.type === _type + }) as Array + } + return undefined + } +} diff --git a/packages/networks/sui/src/models/index.ts b/packages/networks/sui/src/models/index.ts new file mode 100644 index 0000000..354aa74 --- /dev/null +++ b/packages/networks/sui/src/models/index.ts @@ -0,0 +1,5 @@ +export * from './Transaction' +export * from './NftTransaction' +export * from './CoinTransaction' +export * from './TokenTransaction' +export * from './ContractTransaction' diff --git a/packages/networks/sui/src/services/Provider.ts b/packages/networks/sui/src/services/Provider.ts new file mode 100644 index 0000000..84fbf48 --- /dev/null +++ b/packages/networks/sui/src/services/Provider.ts @@ -0,0 +1,168 @@ +import { + ErrorTypeEnum, + type NetworkConfigInterface, + type ProviderInterface +} from '@multiplechain/types' +import { checkWebSocket } from '@multiplechain/utils' +import { SuiClient, SuiHTTPTransport } from '@mysten/sui/client' + +export interface SolanaNodeInfoInterface { + name: string + wsUrl?: string + rpcUrl: string + explorerUrl: string + mode: 'mainnet' | 'devnet' +} + +export type SolanaNodeInfoListInterface = Record + +export class Provider implements ProviderInterface { + /** + * Network configuration of the provider + */ + network: NetworkConfigInterface + + /** + * Node list + */ + nodes: SolanaNodeInfoListInterface = { + mainnet: { + name: 'Mainnet', + mode: 'mainnet', + wsUrl: 'wss://rpc.mainnet.sui.io:443', + rpcUrl: 'https://fullnode.mainnet.sui.io:443', + explorerUrl: 'https://suiscan.xyz/mainnet/' + }, + devnet: { + name: 'Devnet', + mode: 'devnet', + wsUrl: 'wss://rpc.devnet.sui.io:443', + rpcUrl: 'https://fullnode.devnet.sui.io:443', + explorerUrl: 'https://suiscan.xyz/devnet/' + } + } + + /** + * Node information + */ + node: SolanaNodeInfoInterface + + /** + * Sui client + */ + client: SuiClient + + /** + * Transport + */ + transport: SuiHTTPTransport + + /** + * Static instance of the provider + */ + private static _instance: Provider + + /** + * @param network - Network configuration of the provider + */ + constructor(network: NetworkConfigInterface) { + this.update(network) + } + + /** + * Get the static instance of the provider + * @returns Provider instance + */ + static get instance(): Provider { + if (Provider._instance === undefined) { + throw new Error(ErrorTypeEnum.PROVIDER_IS_NOT_INITIALIZED) + } + return Provider._instance + } + + /** + * Initialize the static instance of the provider + * @param network - Network configuration of the provider + */ + static initialize(network: NetworkConfigInterface): void { + if (Provider._instance !== undefined) { + throw new Error(ErrorTypeEnum.PROVIDER_IS_ALREADY_INITIALIZED) + } + Provider._instance = new Provider(network) + } + + /** + * Check RPC connection + * @param url - RPC URL + * @returns RPC connection status + */ + async checkRpcConnection(url?: string): Promise { + try { + const client = new SuiClient({ url: url ?? this.node.rpcUrl }) + await client.getObject({ + id: '0x1' + }) + return true + } catch (error) { + return error as any + } + } + + /** + * Check WS connection + * @param url - Websocket URL + * @returns ws connection status + */ + async checkWsConnection(url?: string): Promise { + try { + const wsUrl = url ?? this.node.wsUrl ?? '' + + if (wsUrl === '' || wsUrl === undefined) { + return new Error(ErrorTypeEnum.WS_URL_NOT_DEFINED) + } + + const result: any = await checkWebSocket(wsUrl) + + if (result instanceof Error) { + return result + } + + return true + } catch (error) { + return error as Error + } + } + + /** + * Update network configuration of the provider + * @param network - Network configuration + */ + update(network: NetworkConfigInterface): void { + this.network = network + Provider._instance = this + this.node = this.nodes[network.testnet ?? false ? 'devnet' : 'mainnet'] + this.node.rpcUrl = this.network.rpcUrl ?? this.node.rpcUrl + this.node.wsUrl = this.network.wsUrl ?? this.node.wsUrl + this.transport = new SuiHTTPTransport({ + url: this.node.rpcUrl, + rpc: { + url: this.node.rpcUrl + }, + websocket: { + url: this.node.wsUrl + } + }) + this.client = new SuiClient({ + network: this.node.mode, + transport: this.transport + }) + } + + /** + * Get the current network configuration is testnet or not + * @returns testnet status + */ + isTestnet(): boolean { + return this.network?.testnet ?? false + } +} diff --git a/packages/networks/sui/src/services/TransactionListener.ts b/packages/networks/sui/src/services/TransactionListener.ts new file mode 100644 index 0000000..80af3c2 --- /dev/null +++ b/packages/networks/sui/src/services/TransactionListener.ts @@ -0,0 +1,156 @@ +import { Provider } from './Provider' +import type { + TransactionTypeEnum, + DynamicTransactionType, + TransactionListenerInterface, + DynamicTransactionListenerFilterType, + TransactionId +} from '@multiplechain/types' +import type { + Transaction, + TokenTransaction, + CoinTransaction, + ContractTransaction, + NftTransaction +} from '../models/index' +import { TransactionListenerProcessIndex } from '@multiplechain/types' + +type TransactionListenerTriggerType = DynamicTransactionType< + T, + Transaction, + ContractTransaction, + CoinTransaction, + TokenTransaction, + NftTransaction +> + +type TransactionListenerCallbackType< + T extends TransactionTypeEnum, + Transaction = TransactionListenerTriggerType +> = (transaction: Transaction) => void + +export class TransactionListener< + T extends TransactionTypeEnum, + DTransaction extends TransactionListenerTriggerType, + CallBackType extends TransactionListenerCallbackType +> implements TransactionListenerInterface +{ + /** + * Transaction type + */ + type: T + + /** + * Provider + */ + provider: Provider + + /** + * Listener status + */ + status: boolean = false + + /** + * Transaction listener callback + */ + callbacks: CallBackType[] = [] + + /** + * Triggered transactions + */ + triggeredTransactions: TransactionId[] = [] + + /** + * Transaction listener filter + */ + filter?: DynamicTransactionListenerFilterType | Record + + /** + * @param type - Transaction type + * @param filter - Transaction listener filter + * @param provider - Provider + */ + constructor(type: T, filter?: DynamicTransactionListenerFilterType, provider?: Provider) { + this.type = type + this.filter = filter ?? {} + this.provider = provider ?? Provider.instance + throw new Error('This class is not implemented for Sui') + } + + /** + * Close the listener + */ + stop(): void { + if (this.status) { + this.status = false + // stop the listener + } + } + + /** + * Start the listener + */ + start(): void { + if (!this.status) { + this.status = true + // @ts-expect-error allow dynamic access + this[TransactionListenerProcessIndex[this.type]]() + } + } + + /** + * Get the listener status + * @returns Listener status + */ + getStatus(): boolean { + return this.status + } + + /** + * Listen to the transaction events + * @param callback - Transaction listener callback + * @returns listener status + */ + async on(callback: CallBackType): Promise { + this.callbacks.push(callback) + return true + } + + /** + * Trigger the event when a transaction is detected + * @param transaction - Transaction data + */ + trigger(transaction: TransactionListenerTriggerType): void { + if (!this.triggeredTransactions.includes(transaction.id)) { + this.triggeredTransactions.push(transaction.id) + this.callbacks.forEach((callback) => { + callback(transaction as unknown as DTransaction) + }) + } + } + + /** + * General transaction process + */ + generalProcess(): void {} + + /** + * Contract transaction process + */ + contractProcess(): void {} + + /** + * Coin transaction process + */ + coinProcess(): void {} + + /** + * Token transaction process + */ + tokenProcess(): void {} + + /** + * NFT transaction process + */ + nftProcess(): void {} +} diff --git a/packages/networks/sui/src/services/TransactionSigner.ts b/packages/networks/sui/src/services/TransactionSigner.ts new file mode 100644 index 0000000..29c4c2a --- /dev/null +++ b/packages/networks/sui/src/services/TransactionSigner.ts @@ -0,0 +1,73 @@ +import { Provider } from '../services/Provider' +import type { Transaction } from '@mysten/sui/transactions' +import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519' +import type { SignatureWithBytes } from '@mysten/sui/cryptography' +import type { PrivateKey, TransactionId, TransactionSignerInterface } from '@multiplechain/types' + +export class TransactionSigner + implements TransactionSignerInterface +{ + /** + * Transaction data from the blockchain network + */ + rawData: Transaction + + /** + * Signed transaction data + */ + signedData: SignatureWithBytes + + /** + * Blockchain network provider + */ + provider: Provider + + /** + * @param rawData - Transaction data + * @param provider - Blockchain network provider + */ + constructor(rawData: Transaction, provider?: Provider) { + this.rawData = rawData + this.provider = provider ?? Provider.instance + } + + /** + * Sign the transaction + * @param privateKey - Transaction data + * @returns Signed transaction data + */ + async sign(privateKey: PrivateKey): Promise { + const keypair = Ed25519Keypair.fromSecretKey(privateKey) + this.rawData.setSenderIfNotSet(keypair.toSuiAddress()) + this.signedData = await keypair.signTransaction( + await this.rawData.build({ client: this.provider.client }) + ) + return this + } + + /** + * Send the transaction to the blockchain network + * @returns Transaction ID + */ + async send(): Promise { + const { digest } = await this.provider.client.executeTransactionBlock({ + transactionBlock: this.signedData.bytes, + signature: this.signedData.signature + }) + return digest + } + + /** + * @returns raw transaction data + */ + getRawData(): Transaction { + return this.rawData + } + + /** + * @returns signed transaction data + */ + getSignedData(): SignatureWithBytes { + return this.signedData + } +} diff --git a/packages/networks/sui/src/services/index.ts b/packages/networks/sui/src/services/index.ts new file mode 100644 index 0000000..0b05d2e --- /dev/null +++ b/packages/networks/sui/src/services/index.ts @@ -0,0 +1,2 @@ +export * from './TransactionSigner' +export * from './TransactionListener' diff --git a/packages/networks/sui/src/utils.ts b/packages/networks/sui/src/utils.ts new file mode 100644 index 0000000..3e129ee --- /dev/null +++ b/packages/networks/sui/src/utils.ts @@ -0,0 +1,12 @@ +import { math } from '@multiplechain/utils' +import { MIST_PER_SUI, SUI_DECIMALS } from '@mysten/sui/utils' + +export * from '@multiplechain/utils' + +export const fromMist = (amount: number | string): number => { + return math.div(Number(amount), Number(MIST_PER_SUI), SUI_DECIMALS) +} + +export const tomMist = (amount: number): number => { + return math.mul(amount, Number(MIST_PER_SUI), SUI_DECIMALS) +} diff --git a/packages/networks/sui/tests/assets.spec.ts b/packages/networks/sui/tests/assets.spec.ts new file mode 100644 index 0000000..92d336e --- /dev/null +++ b/packages/networks/sui/tests/assets.spec.ts @@ -0,0 +1,266 @@ +import { describe, it, expect, assert } from 'vitest' + +import { NFT } from '../src/assets/NFT' +import { Coin } from '../src/assets/Coin' +import { Token } from '../src/assets/Token' +import { math } from '@multiplechain/utils' +import { Transaction } from '../src/models/Transaction' +import { TransactionStatusEnum, type TransactionId } from '@multiplechain/types' +import { TransactionSigner } from '../src/services/TransactionSigner' + +const coinBalanceTestAmount = Number(process.env.SUI_COIN_BALANCE_TEST_AMOUNT) +const tokenBalanceTestAmount = Number(process.env.SUI_TOKEN_BALANCE_TEST_AMOUNT) +const nftBalanceTestAmount = Number(process.env.SUI_NFT_BALANCE_TEST_AMOUNT) +const transferTestAmount = Number(process.env.SUI_TRANSFER_TEST_AMOUNT) +const tokenTransferTestAmount = Number(process.env.SUI_TOKEN_TRANSFER_TEST_AMOUNT) +// const tokenApproveTestAmount = Number(process.env.SUI_TOKEN_APPROVE_TEST_AMOUNT) + +const coinTransferTestIsActive = Boolean(process.env.SUI_COIN_TRANSFER_TEST_IS_ACTIVE !== 'false') +const tokenTransferTestIsActive = Boolean(process.env.SUI_TOKEN_TRANSFER_TEST_IS_ACTIVE !== 'false') +// const tokenApproveTestIsActive = Boolean(process.env.SUI_TOKEN_APPROVE_TEST_IS_ACTIVE !== 'false') +const nftTransactionTestIsActive = Boolean( + process.env.SUI_NFT_TRANSACTION_TEST_IS_ACTIVE !== 'false' +) +// const tokenTransferFromTestIsActive = Boolean( +// process.env.SUI_TOKEN_TRANSFER_FROM_TEST_IS_ACTIVE !== 'false' +// ) + +const balanceTestAddress = String(process.env.SUI_BALANCE_TEST_ADDRESS) +const senderPrivateKey = String(process.env.SUI_SENDER_PRIVATE_KEY) +const receiverPrivateKey = String(process.env.SUI_RECEIVER_PRIVATE_KEY) +const senderTestAddress = String(process.env.SUI_SENDER_TEST_ADDRESS) +const receiverTestAddress = String(process.env.SUI_RECEIVER_TEST_ADDRESS) +const tokenTestAddress = String(process.env.SUI_TOKEN_TYPE_ADDRESS) +const nftTestAddress = String(process.env.SUI_NFT_TYPE_ADDRESS) +const nftObjectId = String(process.env.SUI_NFT_OBJECT_ID) +const nftBalanceObjectId = String(process.env.SUI_MODEL_NFT_OBJECT_ID) + +const waitSecondsBeforeThanNewTx = async (seconds: number): Promise => { + return await new Promise((resolve) => setTimeout(resolve, seconds * 1000)) +} + +const checkSigner = async (signer: TransactionSigner, privateKey?: string): Promise => { + expect(signer).toBeInstanceOf(TransactionSigner) + + const rawData = signer.getRawData() + + assert.isObject(rawData) + + await signer.sign(privateKey ?? senderPrivateKey) + + assert.isObject(signer.getSignedData()) +} + +const checkTx = async (transactionId: TransactionId): Promise => { + const transaction = new Transaction(transactionId) + const status = await transaction.wait(10000) + expect(status).toBe(TransactionStatusEnum.CONFIRMED) +} + +describe('Coin', () => { + const coin = new Coin() + it('Name and symbol', () => { + expect(coin.getName()).toBe('Sui') + expect(coin.getSymbol()).toBe('SUI') + }) + + it('Decimals', () => { + expect(coin.getDecimals()).toBe(9) + }) + + it('Balance', async () => { + expect(await coin.getBalance(balanceTestAddress)).toBe(coinBalanceTestAmount) + }) + + it('Transfer', async () => { + const signer = await coin.transfer( + senderTestAddress, + receiverTestAddress, + transferTestAmount + ) + + await checkSigner(signer) + + if (!coinTransferTestIsActive) return + + const beforeBalance = await coin.getBalance(receiverTestAddress) + + await checkTx(await signer.send()) + + const afterBalance = await coin.getBalance(receiverTestAddress) + expect(afterBalance).toBe(math.add(beforeBalance, transferTestAmount)) + }) +}) + +describe('Token', () => { + const token = new Token(tokenTestAddress) + + it('Name and symbol', async () => { + expect(await token.getName()).toBe('Test USDC') + expect(await token.getSymbol()).toBe('TUSDC') + }) + + it('Decimals', async () => { + expect(await token.getDecimals()).toBe(6) + }) + + it('Balance', async () => { + expect(await token.getBalance(balanceTestAddress)).toBe(tokenBalanceTestAmount) + }) + + it('Total supply', async () => { + const totalSupply = await token.getTotalSupply() + expect(totalSupply).toBe(100000000) + }) + + it('Transfer', async () => { + const signer = await token.transfer( + senderTestAddress, + receiverTestAddress, + tokenTransferTestAmount + ) + + await checkSigner(signer) + + if (!tokenTransferTestIsActive) return + + await waitSecondsBeforeThanNewTx(5) + + const beforeBalance = await token.getBalance(receiverTestAddress) + + await checkTx(await signer.send()) + + const afterBalance = await token.getBalance(receiverTestAddress) + expect(afterBalance).toBe(math.add(beforeBalance, tokenTransferTestAmount)) + }) + + // it('Approve and Allowance', async () => { + // const signer = await token.approve( + // senderTestAddress, + // receiverTestAddress, + // tokenApproveTestAmount + // ) + + // await checkSigner(signer) + + // if (!tokenApproveTestIsActive) return + + // await waitSecondsBeforeThanNewTx(5) + + // await checkTx(await signer.send()) + + // expect(await token.getAllowance(senderTestAddress, receiverTestAddress)).toBe( + // tokenApproveTestAmount + // ) + // }) + + // it('Transfer from', async () => { + // const signer = await token.transferFrom( + // receiverTestAddress, + // senderTestAddress, + // receiverTestAddress, + // 2 + // ) + + // await checkSigner(signer, receiverPrivateKey) + + // if (!tokenTransferFromTestIsActive) return + + // await waitSecondsBeforeThanNewTx(5) + + // const beforeBalance = await token.getBalance(receiverTestAddress) + + // await checkTx(await signer.send()) + + // const afterBalance = await token.getBalance(receiverTestAddress) + // expect(afterBalance).toBe(math.add(beforeBalance, 2)) + // }) +}) + +describe('Nft', () => { + const nft = new NFT(nftTestAddress) + + it('Name and symbol', async () => { + expect(await nft.getName(nftObjectId)).toBe('Test NFT 1') + expect(await nft.getSymbol(nftObjectId)).toBe('Test NFT 1') + }) + + it('Balance', async () => { + const balance = await nft.getBalance(balanceTestAddress) + expect(balance).toBe(nftBalanceTestAmount) + }) + + it('Owner', async () => { + expect(await nft.getOwner(nftBalanceObjectId)).toBe(balanceTestAddress) + }) + + it('Token URI', async () => { + expect(await nft.getTokenURI(nftObjectId)).toBe( + 'https://i.pinimg.com/736x/b6/51/40/b651403a18268e29a362121ab58541ce.jpg' + ) + }) + + // it('Approved', async () => { + // expect(await nft.getApproved(nftObjectId)).toBe(null) + // }) + + it('Transfer', async () => { + await waitSecondsBeforeThanNewTx(1) + const signer = await nft.transfer(senderTestAddress, receiverTestAddress, nftObjectId) + + await checkSigner(signer) + + if (!nftTransactionTestIsActive) return + + await waitSecondsBeforeThanNewTx(5) + + await checkTx(await signer.send()) + + expect(await nft.getOwner(nftObjectId)).toBe(receiverTestAddress) + + const signer2 = await nft.transfer(receiverTestAddress, senderTestAddress, nftObjectId) + + await checkSigner(signer2, receiverPrivateKey) + + await waitSecondsBeforeThanNewTx(5) + + await checkTx(await signer2.send()) + }) + + // it('Approve', async () => { + // const customOwner = nftTransactionTestIsActive ? receiverTestAddress : senderTestAddress + // const customSpender = nftTransactionTestIsActive ? senderTestAddress : receiverTestAddress + // const customPrivateKey = nftTransactionTestIsActive ? receiverPrivateKey : senderPrivateKey + + // const signer = await nft.approve(customOwner, customSpender, nftTransferId) + + // await checkSigner(signer, customPrivateKey) + + // if (!nftTransactionTestIsActive) return + + // await waitSecondsBeforeThanNewTx(5) + + // await checkTx(await signer.send()) + + // expect(await nft.getApproved(nftTransferId)).toBe(senderTestAddress) + // }) + + // it('Transfer from', async () => { + // if (!nftTransactionTestIsActive) return + + // await waitSecondsBeforeThanNewTx(5) + + // const signer = await nft.transferFrom( + // senderTestAddress, + // receiverTestAddress, + // senderTestAddress, + // nftTransferId + // ) + + // await checkSigner(signer) + + // await checkTx(await signer.send()) + + // expect(await nft.getOwner(nftTransferId)).toBe(senderTestAddress) + // }) +}) diff --git a/packages/networks/sui/tests/models.spec.ts b/packages/networks/sui/tests/models.spec.ts new file mode 100644 index 0000000..86a8f40 --- /dev/null +++ b/packages/networks/sui/tests/models.spec.ts @@ -0,0 +1,160 @@ +import { describe, it, expect } from 'vitest' + +import { Transaction } from '../src/models/Transaction' +import { NftTransaction } from '../src/models/NftTransaction' +import { CoinTransaction } from '../src/models/CoinTransaction' +import { TokenTransaction } from '../src/models/TokenTransaction' +import { AssetDirectionEnum, TransactionStatusEnum } from '@multiplechain/types' + +const nftObjectId = String(process.env.SUI_MODEL_NFT_OBJECT_ID) +const tokenAmount = Number(process.env.SUI_MODEL_TOKEN_AMOUNT) +const coinAmount = Number(process.env.SUI_MODEL_COIN_AMOUNT) + +const tokenType = String(process.env.SUI_TOKEN_TYPE_ADDRESS) +const nftType = String(process.env.SUI_NFT_TYPE_ADDRESS) + +const suiTransferTx = String(process.env.SUI_TRANSFER_TX) +const tokenTransferTx = String(process.env.SUI_TOKEN_TRANSFER_TX) +const nftTransferTx = String(process.env.SUI_NFT_TRANSFER_TX) + +const sender = String(process.env.SUI_MODEL_TEST_SENDER) +const receiver = String(process.env.SUI_MODEL_TEST_RECEIVER) + +describe('Transaction', () => { + const tx = new Transaction(suiTransferTx) + it('Id', async () => { + expect(tx.getId()).toBe(suiTransferTx) + }) + + it('Data', async () => { + expect(await tx.getData()).toBeTypeOf('object') + }) + + it('Wait', async () => { + expect(await tx.wait()).toBe(TransactionStatusEnum.CONFIRMED) + }) + + it('URL', async () => { + expect(tx.getUrl()).toBe( + 'https://suiscan.xyz/devnet/tx/22exMwAKinLgGjd9RqawzYmFb7XUhLnvGWXsLFXgBYRH' + ) + }) + + it('Sender', async () => { + expect(await tx.getSigner()).toBe(sender) + }) + + it('Fee', async () => { + expect(await tx.getFee()).toBe(0.00199788) + }) + + it('Block Number', async () => { + expect(await tx.getBlockNumber()).toBe(1040686) + }) + + it('Block Timestamp', async () => { + expect(await tx.getBlockTimestamp()).toBe(1745471638189) + }) + + it('Block Confirmation Count', async () => { + expect(await tx.getBlockConfirmationCount()).toBeGreaterThan(45397) + }) + + it('Status', async () => { + expect(await tx.getStatus()).toBe(TransactionStatusEnum.CONFIRMED) + }) +}) + +describe('Coin Transaction', () => { + const tx = new CoinTransaction(suiTransferTx) + + it('Receiver', async () => { + expect((await tx.getReceiver()).toLowerCase()).toBe(receiver.toLowerCase()) + }) + + it('Amount', async () => { + expect(await tx.getAmount()).toBe(coinAmount) + }) + + it('Verify Transfer', async () => { + expect(await tx.verifyTransfer(AssetDirectionEnum.INCOMING, receiver, coinAmount)).toBe( + TransactionStatusEnum.CONFIRMED + ) + + expect(await tx.verifyTransfer(AssetDirectionEnum.OUTGOING, sender, coinAmount)).toBe( + TransactionStatusEnum.CONFIRMED + ) + + expect(await tx.verifyTransfer(AssetDirectionEnum.OUTGOING, receiver, coinAmount)).toBe( + TransactionStatusEnum.FAILED + ) + }) +}) + +describe('Token Transaction', () => { + const tx = new TokenTransaction(tokenTransferTx) + + it('Receiver', async () => { + expect((await tx.getReceiver()).toLowerCase()).toBe(receiver.toLowerCase()) + }) + + it('Amount', async () => { + expect(await tx.getAmount()).toBe(tokenAmount) + }) + + it('Address', async () => { + expect(await tx.getAddress()).toBe(tokenType) + }) + + it('Verify Transfer', async () => { + expect(await tx.verifyTransfer(AssetDirectionEnum.INCOMING, receiver, tokenAmount)).toBe( + TransactionStatusEnum.CONFIRMED + ) + + expect(await tx.verifyTransfer(AssetDirectionEnum.OUTGOING, sender, tokenAmount)).toBe( + TransactionStatusEnum.CONFIRMED + ) + + expect(await tx.verifyTransfer(AssetDirectionEnum.OUTGOING, receiver, tokenAmount)).toBe( + TransactionStatusEnum.FAILED + ) + }) +}) + +describe('NFT Transaction', () => { + const tx = new NftTransaction(nftTransferTx) + + it('Receiver', async () => { + expect((await tx.getReceiver()).toLowerCase()).toBe(receiver.toLowerCase()) + }) + + it('Address', async () => { + expect(await tx.getAddress()).toBe(nftType) + }) + + it('Signer', async () => { + expect((await tx.getSigner()).toLowerCase()).toBe(sender.toLowerCase()) + }) + + it('Sender', async () => { + expect((await tx.getSender()).toLowerCase()).toBe(sender.toLowerCase()) + }) + + it('NFT ID', async () => { + expect(await tx.getNftId()).toBe(nftObjectId) + }) + + it('Verify Transfer', async () => { + expect(await tx.verifyTransfer(AssetDirectionEnum.INCOMING, receiver, nftObjectId)).toBe( + TransactionStatusEnum.CONFIRMED + ) + + expect(await tx.verifyTransfer(AssetDirectionEnum.OUTGOING, sender, nftObjectId)).toBe( + TransactionStatusEnum.CONFIRMED + ) + + expect(await tx.verifyTransfer(AssetDirectionEnum.OUTGOING, receiver, nftObjectId)).toBe( + TransactionStatusEnum.FAILED + ) + }) +}) diff --git a/packages/networks/sui/tests/services.spec.ts b/packages/networks/sui/tests/services.spec.ts new file mode 100644 index 0000000..a566f56 --- /dev/null +++ b/packages/networks/sui/tests/services.spec.ts @@ -0,0 +1,216 @@ +import { describe, it, expect } from 'vitest' + +import { provider } from './setup' +import { Provider } from '../src/services/Provider' + +import { NFT } from '../src/assets/NFT' +import { Coin } from '../src/assets/Coin' +import { Token } from '../src/assets/Token' +import { sleep } from '@multiplechain/utils' +import { TransactionTypeEnum } from '@multiplechain/types' +import { Transaction } from '../src/models/Transaction' +import { NftTransaction } from '../src/models/NftTransaction' +import { CoinTransaction } from '../src/models/CoinTransaction' +import { TokenTransaction } from '../src/models/TokenTransaction' +import { ContractTransaction } from '../src/models/ContractTransaction' +import { TransactionListener } from '../src/services/TransactionListener' + +const senderPrivateKey = String(process.env.SUI_SENDER_PRIVATE_KEY) +const receiverPrivateKey = String(process.env.SUI_RECEIVER_PRIVATE_KEY) +const senderTestAddress = String(process.env.SUI_SENDER_TEST_ADDRESS) +const receiverTestAddress = String(process.env.SUI_RECEIVER_TEST_ADDRESS) +const tokenTestAddress = String(process.env.SUI_TOKEN_TEST_ADDRESS) +const tokenProgram = String(process.env.SUI_TOKEN_PROGRAM) +const nftTestAddress = String(process.env.SUI_NFT_TEST_ADDRESS) +const nftTransferId = String(process.env.SUI_NFT_TRANSFER_ID) + +const transactionListenerTestIsActive = Boolean( + process.env.SUI_TRANSACTION_LISTENER_TEST_IS_ACTIVE !== 'false' +) + +const waitSecondsBeforeThanNewTx = async (seconds: number): Promise => { + return await new Promise((resolve) => setTimeout(resolve, seconds * 1000)) +} + +describe('Provider', () => { + it('isTestnet', () => { + expect(provider.isTestnet()).toBe(true) + }) + + it('instance', () => { + expect(Provider.instance).toBe(provider) + }) + + it('checkRpcConnection', async () => { + expect(await provider.checkRpcConnection()).toBe(true) + }) + + it('checkWsConnection', async () => { + expect(await provider.checkWsConnection()).not.toBe(true) + }) +}) + +describe('Transaction Listener', () => { + if (!transactionListenerTestIsActive) { + it('No test is active', () => { + expect(true).toBe(true) + }) + return + } + + it('General', async () => { + const listener = new TransactionListener(TransactionTypeEnum.GENERAL, { + signer: senderTestAddress + }) + + const signer = await new Coin().transfer(senderTestAddress, receiverTestAddress, 0.0001) + + const waitListenerEvent = async (): Promise => { + return await new Promise((resolve, reject) => { + void listener + .on((transaction) => { + listener.stop() + resolve(transaction) + }) + .then(async () => { + await sleep(2000) + void (await signer.sign(senderPrivateKey)).send() + }) + .catch(reject) + }) + } + + expect(await waitListenerEvent()).toBeInstanceOf(Transaction) + }) + + it('Contract', async () => { + await waitSecondsBeforeThanNewTx(10) + + const listener = new TransactionListener(TransactionTypeEnum.CONTRACT, { + signer: senderTestAddress, + address: tokenProgram + }) + + const signer = await new Token(tokenTestAddress).transfer( + senderTestAddress, + receiverTestAddress, + 0.01 + ) + + const waitListenerEvent = async (): Promise => { + return await new Promise((resolve, reject) => { + void listener + .on((transaction) => { + listener.stop() + resolve(transaction) + }) + .then(async () => { + await sleep(2000) + void (await signer.sign(senderPrivateKey)).send() + }) + .catch(reject) + }) + } + + expect(await waitListenerEvent()).toBeInstanceOf(ContractTransaction) + }) + + it('Coin', async () => { + await waitSecondsBeforeThanNewTx(10) + + const listener = new TransactionListener(TransactionTypeEnum.COIN, { + signer: senderTestAddress, + receiver: receiverTestAddress + }) + + const signer = await new Coin().transfer(senderTestAddress, receiverTestAddress, 0.0001) + + const waitListenerEvent = async (): Promise => { + return await new Promise((resolve, reject) => { + void listener + .on((transaction) => { + listener.stop() + resolve(transaction) + }) + .then(async () => { + await sleep(2000) + void (await signer.sign(senderPrivateKey)).send() + }) + .catch(reject) + }) + } + + expect(await waitListenerEvent()).toBeInstanceOf(CoinTransaction) + }) + + it('Token', async () => { + await waitSecondsBeforeThanNewTx(10) + + const listener = new TransactionListener(TransactionTypeEnum.TOKEN, { + signer: senderTestAddress, + receiver: receiverTestAddress, + address: tokenTestAddress + }) + + const signer = await new Token(tokenTestAddress).transfer( + senderTestAddress, + receiverTestAddress, + 0.01 + ) + + const waitListenerEvent = async (): Promise => { + return await new Promise((resolve, reject) => { + void listener + .on((transaction) => { + listener.stop() + resolve(transaction) + }) + .then(async () => { + await sleep(2000) + void (await signer.sign(senderPrivateKey)).send() + }) + .catch(reject) + }) + } + + expect(await waitListenerEvent()).toBeInstanceOf(TokenTransaction) + }) + + it('NFT', async () => { + await waitSecondsBeforeThanNewTx(10) + + const listener = new TransactionListener(TransactionTypeEnum.NFT, { + signer: senderTestAddress, + receiver: receiverTestAddress, + address: nftTestAddress, + nftId: nftTransferId + }) + + const nft = new NFT(nftTestAddress) + const signer = await nft.transfer(senderTestAddress, receiverTestAddress, nftTransferId) + + const waitListenerEvent = async (): Promise => { + return await new Promise((resolve, reject) => { + void listener + .on((transaction) => { + listener.stop() + resolve(transaction) + }) + .then(async () => { + await sleep(2000) + void (await signer.sign(senderPrivateKey)).send() + }) + .catch(reject) + }) + } + + const transaction = await waitListenerEvent() + expect(transaction).toBeInstanceOf(NftTransaction) + await transaction.wait() + await waitSecondsBeforeThanNewTx(10) + + const newSigner = await nft.transfer(receiverTestAddress, senderTestAddress, nftTransferId) + + await (await newSigner.sign(receiverPrivateKey)).send() + }) +}) diff --git a/packages/networks/sui/tests/setup.ts b/packages/networks/sui/tests/setup.ts new file mode 100644 index 0000000..6cd3644 --- /dev/null +++ b/packages/networks/sui/tests/setup.ts @@ -0,0 +1,15 @@ +import { Provider } from '../src/services/Provider' + +let provider: Provider + +try { + provider = Provider.instance +} catch (e) { + provider = new Provider({ + testnet: true, + wsUrl: 'wss://rpc.devnet.sui.io:443', + rpcUrl: 'https://fullnode.devnet.sui.io:443' + }) +} + +export { provider } diff --git a/packages/networks/sui/tsconfig.json b/packages/networks/sui/tsconfig.json new file mode 100644 index 0000000..594d173 --- /dev/null +++ b/packages/networks/sui/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "noEmit": true, + "composite": true, + "declaration": true, + "outDir": "./dist/esm", + "declarationDir": "./dist/types" + }, + "extends": "../../../tsconfig.json", + "include": [ + "src", + ".eslintrc.json", + "tests", + "vite.config.ts", + "esbuild.ts", + "vitest.config.ts", + "../../../esbuild.ts", + "../../../vite.config.ts", + "../../../vitest.config.ts" + ] +} diff --git a/packages/networks/sui/vite.config.ts b/packages/networks/sui/vite.config.ts new file mode 100644 index 0000000..cdfecee --- /dev/null +++ b/packages/networks/sui/vite.config.ts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vite' +import mainConfig from '../../../vite.config' + +export default mergeConfig(mainConfig, { + build: { + lib: { + name: 'MultipleChain.Sui' + } + } +}) diff --git a/packages/networks/sui/vite.svg b/packages/networks/sui/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/packages/networks/sui/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/networks/sui/vitest.config.ts b/packages/networks/sui/vitest.config.ts new file mode 100644 index 0000000..94ef2da --- /dev/null +++ b/packages/networks/sui/vitest.config.ts @@ -0,0 +1,12 @@ +import { mergeConfig, defineConfig } from 'vitest/config' +import mainConfig from '../../../vite.config' + +export default mergeConfig( + mainConfig, + defineConfig({ + test: { + testTimeout: 180000, + setupFiles: ['./tests/setup.ts'] + } + }) +) diff --git a/packages/networks/ton/package.json b/packages/networks/ton/package.json index 8f5363a..1e7eca7 100644 --- a/packages/networks/ton/package.json +++ b/packages/networks/ton/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/ton", - "version": "0.1.11", + "version": "0.4.20", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", diff --git a/packages/networks/tron/package.json b/packages/networks/tron/package.json index 1019406..04b0870 100644 --- a/packages/networks/tron/package.json +++ b/packages/networks/tron/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/tron", - "version": "0.4.18", + "version": "0.4.20", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", diff --git a/packages/networks/xrpl/package.json b/packages/networks/xrpl/package.json index 20de416..7048246 100644 --- a/packages/networks/xrpl/package.json +++ b/packages/networks/xrpl/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/xrpl", - "version": "0.1.2", + "version": "0.4.20", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", diff --git a/packages/networks/xrpl/src/browser/adapters/MetaMask.ts b/packages/networks/xrpl/src/browser/adapters/MetaMask.ts index 2ffc73f..fb2d4ce 100644 --- a/packages/networks/xrpl/src/browser/adapters/MetaMask.ts +++ b/packages/networks/xrpl/src/browser/adapters/MetaMask.ts @@ -29,7 +29,7 @@ const MetaMask: WalletAdapterInterface = { name: 'MetaMask Snap', icon: metaMask, downloadLink: 'https://metamask.io/download/', - platforms: [WalletPlatformEnum.BROWSER, WalletPlatformEnum.MOBILE], + platforms: [WalletPlatformEnum.BROWSER], isDetected: () => { return Boolean((window?.ethereum as unknown as WindowEthereum)?.isMetaMask) }, diff --git a/tsconfig.json b/tsconfig.json index 19d76df..363e48d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,7 @@ "vite.config.ts", "vitest.config.ts", "vite-env.d.ts", - "esbuild.ts", + "esbuild.ts" ], "exclude": ["node_modules", "dist", "test*.ts"] } diff --git a/vitest.config.ts b/vitest.config.ts index 4e21eec..0223688 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -25,7 +25,7 @@ export default mergeConfig( }, testTimeout: 180000, environment: 'node', - exclude: [...configDefaults.exclude, 'e2e/*', '**/boilerplate/**'], + exclude: [...configDefaults.exclude, 'e2e/*', '**/boilerplate/**', '**/ton/**'], root: fileURLToPath(new URL('./', import.meta.url)), setupFiles: [ './packages/networks/evm-chains/tests/setup.ts', @@ -33,6 +33,7 @@ export default mergeConfig( './packages/networks/solana/tests/setup.ts', './packages/networks/tron/tests/setup.ts', './packages/networks/xrpl/tests/setup.ts', + './packages/networks/sui/tests/setup.ts', './packages/networks/ton/tests/setup.ts' ] }