diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d167274ba22..cb9e88a224c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1471,9 +1471,6 @@ importers: '@iota/wallet-standard': specifier: workspace:* version: link:../wallet-standard - '@iota/zksend': - specifier: workspace:* - version: link:../zksend '@radix-ui/react-dialog': specifier: ^1.0.4 version: 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -1947,37 +1944,6 @@ importers: specifier: ^0.33.0 version: 0.33.0(@vitest/ui@0.33.0)(happy-dom@10.5.1)(jsdom@23.0.0)(sass@1.63.6)(terser@5.31.0) - sdk/zksend: - dependencies: - '@iota/iota.js': - specifier: workspace:* - version: link:../typescript - '@iota/wallet-standard': - specifier: workspace:* - version: link:../wallet-standard - mitt: - specifier: ^3.0.1 - version: 3.0.1 - nanostores: - specifier: ^0.9.3 - version: 0.9.3 - valibot: - specifier: ^0.25.0 - version: 0.25.0 - devDependencies: - '@iota/build-scripts': - specifier: workspace:* - version: link:../build-scripts - '@types/node': - specifier: ^20.4.2 - version: 20.4.2 - typescript: - specifier: ^5.3.3 - version: 5.3.3 - vitest: - specifier: ^0.33.0 - version: 0.33.0(@vitest/ui@0.33.0)(happy-dom@10.5.1)(jsdom@23.0.0)(sass@1.63.6)(terser@5.31.0) - packages: '@0no-co/graphql.web@1.0.7': @@ -14423,9 +14389,6 @@ packages: resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} engines: {node: '>=10.12.0'} - valibot@0.25.0: - resolution: {integrity: sha512-cmD0ca15oyAbT75iYLNW6uU6doAeIwYfOshpXka/E1Bx4frzbkrgb7gvkI7K0YK/DVOksei4FfxWfRoBP3NFTg==} - validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -31401,8 +31364,6 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 1.9.0 - valibot@0.25.0: {} - validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 diff --git a/sdk/dapp-kit/package.json b/sdk/dapp-kit/package.json index d1958878003..6085fbd7674 100644 --- a/sdk/dapp-kit/package.json +++ b/sdk/dapp-kit/package.json @@ -81,7 +81,6 @@ "dependencies": { "@iota/iota.js": "workspace:*", "@iota/wallet-standard": "workspace:*", - "@iota/zksend": "workspace:*", "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-slot": "^1.0.2", diff --git a/sdk/dapp-kit/src/components/WalletProvider.tsx b/sdk/dapp-kit/src/components/WalletProvider.tsx index 67a68400bb9..0dae6322522 100644 --- a/sdk/dapp-kit/src/components/WalletProvider.tsx +++ b/sdk/dapp-kit/src/components/WalletProvider.tsx @@ -18,8 +18,6 @@ import { useAutoConnectWallet } from '../hooks/wallet/useAutoConnectWallet.js'; import { useUnsafeBurnerWallet } from '../hooks/wallet/useUnsafeBurnerWallet.js'; import { useWalletPropertiesChanged } from '../hooks/wallet/useWalletPropertiesChanged.js'; import { useWalletsChanged } from '../hooks/wallet/useWalletsChanged.js'; -import type { ZkSendWalletConfig } from '../hooks/wallet/useZkSendWallet.js'; -import { useZkSendWallet } from '../hooks/wallet/useZkSendWallet.js'; import { lightTheme } from '../themes/lightTheme.js'; import type { Theme } from '../themes/themeContract.js'; import { createInMemoryStore } from '../utils/stateStorage.js'; @@ -40,9 +38,6 @@ export type WalletProviderProps = { /** Enables automatically reconnecting to the most recently used wallet account upon mounting. */ autoConnect?: boolean; - /** Enables the zkSend wallet */ - zkSend?: ZkSendWalletConfig; - /** Configures how the most recently connected to wallet account is stored. Set to `null` to disable persisting state entirely. Defaults to using localStorage if it is available. */ storage?: StateStorage | null; @@ -64,7 +59,6 @@ export function WalletProvider({ storageKey = DEFAULT_STORAGE_KEY, enableUnsafeBurner = false, autoConnect = false, - zkSend, theme = lightTheme, children, }: WalletProviderProps) { @@ -83,7 +77,6 @@ export function WalletProvider({ preferredWallets={preferredWallets} requiredFeatures={requiredFeatures} enableUnsafeBurner={enableUnsafeBurner} - zkSend={zkSend} > {/* TODO: We ideally don't want to inject styles if people aren't using the UI components */} {theme ? : null} @@ -95,19 +88,17 @@ export function WalletProvider({ type WalletConnectionManagerProps = Pick< WalletProviderProps, - 'preferredWallets' | 'requiredFeatures' | 'enableUnsafeBurner' | 'zkSend' | 'children' + 'preferredWallets' | 'requiredFeatures' | 'enableUnsafeBurner' | 'children' >; function WalletConnectionManager({ preferredWallets = DEFAULT_PREFERRED_WALLETS, requiredFeatures = DEFAULT_REQUIRED_FEATURES, enableUnsafeBurner = false, - zkSend, children, }: WalletConnectionManagerProps) { useWalletsChanged(preferredWallets, requiredFeatures); useWalletPropertiesChanged(); - useZkSendWallet(zkSend); useUnsafeBurnerWallet(enableUnsafeBurner); useAutoConnectWallet(); diff --git a/sdk/dapp-kit/src/constants/walletDefaults.ts b/sdk/dapp-kit/src/constants/walletDefaults.ts index 2882bc860ad..be739763898 100644 --- a/sdk/dapp-kit/src/constants/walletDefaults.ts +++ b/sdk/dapp-kit/src/constants/walletDefaults.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 import type { WalletWithRequiredFeatures } from '@iota/wallet-standard'; -import { ZKSEND_WALLET_NAME } from '@iota/zksend'; import { createInMemoryStore } from '../utils/stateStorage.js'; @@ -18,4 +17,4 @@ export const DEFAULT_REQUIRED_FEATURES: (keyof WalletWithRequiredFeatures['featu 'iota:signTransactionBlock', ]; -export const DEFAULT_PREFERRED_WALLETS = [IOTA_WALLET_NAME, ZKSEND_WALLET_NAME]; +export const DEFAULT_PREFERRED_WALLETS = [IOTA_WALLET_NAME]; diff --git a/sdk/dapp-kit/src/hooks/wallet/useZkSendWallet.ts b/sdk/dapp-kit/src/hooks/wallet/useZkSendWallet.ts deleted file mode 100644 index 9146b464619..00000000000 --- a/sdk/dapp-kit/src/hooks/wallet/useZkSendWallet.ts +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import type { ZkSendWallet } from '@iota/zksend'; -import { registerZkSendWallet } from '@iota/zksend'; -import { useEffect, useLayoutEffect, useState } from 'react'; - -import { useAutoConnectWallet } from './useAutoConnectWallet.js'; -import { useConnectWallet } from './useConnectWallet.js'; - -export interface ZkSendWalletConfig { - name: string; - origin?: string; -} - -export function useZkSendWallet(config?: ZkSendWalletConfig) { - const status = useAutoConnectWallet(); - const [address, setAddress] = useState(null); - const [wallet, setWallet] = useState(null); - const { mutate: connect } = useConnectWallet(); - - useEffect(() => { - // This handles an edge case where the user has already connected a wallet, but is coming from - // a zkSend redirect, and we want to force the zkSend wallet to connect. We need to wait for the - // autoconnection to attempt to connect, then force the zkSend wallet to connect. - if (!address || !wallet || status !== 'attempted') return; - - connect({ wallet, silent: true }); - // Reset the address since we only want to do this once: - setAddress(null); - }, [address, status, connect, wallet]); - - useLayoutEffect(() => { - if (!config?.name) { - return; - } - - const { wallet, unregister, addressFromRedirect } = registerZkSendWallet(config.name, { - origin: config.origin, - }); - - if (addressFromRedirect) { - setWallet(wallet); - setAddress(addressFromRedirect); - } - - return unregister; - }, [config?.name, config?.origin]); -} diff --git a/sdk/dapp-kit/tsconfig.json b/sdk/dapp-kit/tsconfig.json index 4e05676fad3..09c39c9c544 100644 --- a/sdk/dapp-kit/tsconfig.json +++ b/sdk/dapp-kit/tsconfig.json @@ -8,9 +8,5 @@ "jsx": "react-jsx", "rootDir": "src" }, - "references": [ - { "path": "../typescript" }, - { "path": "../wallet-standard" }, - { "path": "../zksend" } - ] + "references": [{ "path": "../typescript" }, { "path": "../wallet-standard" }] } diff --git a/sdk/docs/package.json b/sdk/docs/package.json index 722f55ff6a3..e120bc08c35 100644 --- a/sdk/docs/package.json +++ b/sdk/docs/package.json @@ -31,7 +31,6 @@ "@iota/kiosk": "workspace:*", "@iota/iota.js": "workspace:*", "@iota/zklogin": "workspace:*", - "@iota/zksend": "workspace:*", "@tanstack/react-query": "^5.0.0", "@types/node": "^20.4.2", "next": "^13.4.15", diff --git a/sdk/docs/pages/_meta.json b/sdk/docs/pages/_meta.json index 2e75fbf36a8..7f14db0df0e 100644 --- a/sdk/docs/pages/_meta.json +++ b/sdk/docs/pages/_meta.json @@ -11,10 +11,6 @@ "title": "Kiosk SDK", "type": "page" }, - "zksend": { - "title": "zkSend SDK", - "type": "page" - }, "bcs": { "title": "BCS", "type": "page" diff --git a/sdk/docs/pages/dapp-kit/_meta.json b/sdk/docs/pages/dapp-kit/_meta.json index 70b776784d0..1c16272071a 100644 --- a/sdk/docs/pages/dapp-kit/_meta.json +++ b/sdk/docs/pages/dapp-kit/_meta.json @@ -6,6 +6,5 @@ "wallet-provider": "WalletProvider", "wallet-components": "Wallet Components", "wallet-hooks": "Wallet Hooks", - "zksend": "zkSend Integration", "themes": "Themes" } diff --git a/sdk/docs/pages/dapp-kit/wallet-provider.mdx b/sdk/docs/pages/dapp-kit/wallet-provider.mdx index 9038c0fcd44..3222ebfe68b 100644 --- a/sdk/docs/pages/dapp-kit/wallet-provider.mdx +++ b/sdk/docs/pages/dapp-kit/wallet-provider.mdx @@ -29,8 +29,6 @@ All props are optional. - `enableUnsafeBurner` - Enables the development-only unsafe burner wallet, useful for testing. - `autoConnect` - Enables automatically reconnecting to the most recently used wallet account upon mounting. -- `zkSend` - Enables and configures the zkSend wallet. Read more about how to - [use the zkSend integration](./zksend.mdx). - `storage` - Configures how the most recently connected-to wallet account is stored. Set to `null` to disable persisting state entirely. Defaults to using `localStorage` if it is available. diff --git a/sdk/docs/pages/dapp-kit/zksend.mdx b/sdk/docs/pages/dapp-kit/zksend.mdx deleted file mode 100644 index 3a6d794bbe3..00000000000 --- a/sdk/docs/pages/dapp-kit/zksend.mdx +++ /dev/null @@ -1,24 +0,0 @@ -# zkSend Integration - -dApp Kit provides out-of-the-box opt-in support for [zkSend wallets](../zksend/dapp.mdx). - -## Setup - -To enable support for zkSend wallets, pass the `zkSend` object to the `WalletProvider` component. -This object has the following properties: - -- **`name`** - The name of your dApp, shown to the user when connecting to the dApp. - -```tsx -function App({ children }) { - return ( - - {children} - - ); -} -``` diff --git a/sdk/docs/pages/zksend/_meta.json b/sdk/docs/pages/zksend/_meta.json deleted file mode 100644 index b32e4c426d1..00000000000 --- a/sdk/docs/pages/zksend/_meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "index": "Introduction", - "dapp": "dApp Integration", - "link-builder": "Creating zkSend Links" -} diff --git a/sdk/docs/pages/zksend/dapp.mdx b/sdk/docs/pages/zksend/dapp.mdx deleted file mode 100644 index 19af5a00e5c..00000000000 --- a/sdk/docs/pages/zksend/dapp.mdx +++ /dev/null @@ -1,73 +0,0 @@ -import { Callout } from 'nextra/components'; - -# dApp Integration - - - If you are using `dapp-kit`, you do not need to install any additional packages to integrate - with the zkSend wallet. [Read how to integrate with dapp-kit.](../dapp-kit/zksend.mdx) - - -Using the zkSend SDK, you can allow users to connect to the zkSend wallet from your dApp. The wallet -is provided through the [Wallet Standard](https://docs.iota.io/standards/wallet-standard), and should -appear automatically in your existing wallet connection UI. - -## Considerations - -- zkSend only supports mainnet at this time. -- Users will only be able to connect their zkLogin wallet from zkSend. -- The zkLogin account will be managed by zkSend, you do not need to set up any OAuth providers. -- You will not get access to the user's private key, you will only be able to connect to the - wallet, and suggest transactions and messages to sign, just like any other wallet in the - ecosystem. - -## Setup - -To use the zkSend wallet, you will need to register it in your application, using -`registerZkSendWallet`. This only needs to be done once, and should be done as early as possible in -your application's lifecycle. - -`registerZkSendWallet` takes two arguments: - -- `name`: The name of your dApp. This will be shown to the user when they are asked to approve the - connection in zkSend. -- `options`: An optional object with the following properties: - - `origin`: The origin of the zkSend website. Defaults to `https://zksend.com`. - -```ts -import { registerZkSendWallet } from '@iota/zksend'; - -registerZkSendWallet('Your dApp Name'); -``` - -## Supported features - -The zkSend wallet currently supports the following features: - -- `signTransactionBlock` -- `signPersonalMessage` - -We intentionally do not support `signAndExecuteTransactionBlock`, and suggest that your dApp instead -use `signTransactionBlock`, and submit the signed transaction within your dApp instead. This -provides better control around execution, and avoids potential issues around data inconsistency. If -you believe you have a strong reason for using `signAndExecuteTransactionBlock`, please -[open an issue](https://github.com/iotaledger/iota/issues/new/choose). - -## Detecting the zkSend wallet - -If you'd like to detect whether the user is connected to the zkSend wallet, you can use the `name` -property on the wallet - -For example, if you are using `dapp-kit`, you can use the `useCurrentWallet` hook to get the current -wallet, and check if it is the zkSend wallet. - -```ts -import { useCurrentWallet } from '@iota/dapp-kit'; -import { ZKSEND_WALLET_NAME } from '@iota/zksend'; - -function ZkSendOnly() { - const { curentWallet } = useCurrentWallet(); - const walletIsZkSendWallet = curentWallet?.name === ZKSEND_WALLET_NAME; - - // rest of component logic... -} -``` diff --git a/sdk/docs/pages/zksend/index.mdx b/sdk/docs/pages/zksend/index.mdx deleted file mode 100644 index 15b6a9dc161..00000000000 --- a/sdk/docs/pages/zksend/index.mdx +++ /dev/null @@ -1,12 +0,0 @@ -# zkSend SDK - -The zkSend SDK provides tools for interacting with the [zkSend website](https://zksend.com). It -allows dApps to integrate zkSend into their applications by providing a standard wallet interface. -It also provides functionality to create your own zkSend Claim Links, with support for any publicly -transferrable asset. - -## Installation - -```sh npm2yarn -npm i @iota/zksend -``` diff --git a/sdk/docs/pages/zksend/link-builder.mdx b/sdk/docs/pages/zksend/link-builder.mdx deleted file mode 100644 index 2a1586dd579..00000000000 --- a/sdk/docs/pages/zksend/link-builder.mdx +++ /dev/null @@ -1,210 +0,0 @@ -import { Callout } from 'nextra/components'; - -# Creating zkSend Links - - - Products and services that incorporate zkSend links may be subject to financial regulations, - including obtaining money transmitter licenses in jurisdictions where you provide your services. - It is the developer's responsibility to ensure compliance with all relevant laws and obtain any - necessary licenses. The information provided by Mysten Labs is not legal advice, and developers - should consult with a qualified legal professional to address their specific circumstances. - - -## Limitations - -- zkSend only supports mainnet at this time. -- Objects within links must be publicly transferrable. - -## Create a link - -You can start creating your own zkSend link using the `ZkSendLinkBuilder` class. This class -constructor takes an object with the following options: - -- **`sender`** (required) - Required. The address of the sender / creator of the link. -- **`redirect`** (optional) - The redirect configuration for the link. See "Claim redirect" below - for more information. - - **`url`** - The url to redirect to after the link have been claimed. - - **`name`** - The name of your dApp. This will be shown to the user when claiming the link. -- **`client`** (optional) - The `@iota/iota.js` client used to fetch data to construct the link. - If not provided, a default client will be used. - -```ts -import { ZkSendLinkBuilder } from '@iota/zksend'; - -const link = new ZkSendLinkBuilder({ - sender: '0x...', -}); -``` - -#### Adding IOTA to the link - -You can add IOTA to the link by calling `link.addClaimableMicros()`. This method takes the following -params: - -- **`amount`** (required) - The amount of MICROS (the base unit of IOTA) to add to the link. - -#### Adding non-IOTA coins to the link - -You can add non-IOTA coins to the link by calling `link.addClaimableBalance()`. This method takes the -following params: - -- **`coinType`** (required) - The coin type of the coin to add to the link (e.g. `0x2::iota::IOTA`). -- **`amount`** (required) - The amount of the coin to add to the link. Represented in the base - unit of the coin. - -The SDK will automatically perform the necessary coin management logic to transfer the defined -amount, such as merging and splitting coin objects. - -#### Adding objects to the link - -You can add a publicly-transferrable object to the link by calling `link.addClaimableObject()`. This -method takes the following params: - -- **`id`** (required) - The ID of the object. This must be owned by the `sender` you configured - when creating the link. - -#### Getting the link URL - -At any time, you can get the URL for the link by calling `link.getLink()`. - -## Submitting the link transaction - -Once you have built your zkSend link, you need to execute a transaction to transfer assets and make -the link claimable. - -You can call the `link.createSendTransaction()` method, which returns a `TransactionBlock` object -that you can sign and submit to the blockchain. - -```ts -const txb = await link.createSendTransaction(); - -const { bytes, signature } = txb.sign({ client, signer: keypair }); - -const result = await client.executeTransactionBlock({ - transactionBlock: bytes, - signature, -}); -``` - -If you have a keypair you would like to send the transaction with, you can use the `create` method -as shorthand for creating the send transaction, signing it, and submitting it to the blockchain. - -```ts -await link.create({ - signer: yourKeypair, -}); -``` - -## Claim redirect - -You can configure a redirect for the link, which will redirect the user to a URL of your choice -after the link has been claimed. This is useful for dApps that want to use zkSend as an onboarding -tool, and have the user redirected to their dApp after claiming the link. - -To configure a redirect, you need to provide a `redirect` object when creating the link. This object -takes the following params: - -- **`url`** - The URL to redirect to after the link has been claimed. -- **`name`** - The name of your dApp. This will be shown to the user when claiming the link. - -When redirecting to the URL, if the user claims with zkLogin, then zkSend will automatically add a -`zksend_address` query parameter to the URL, containing the Iota address of the user that claimed the -asset. The [zkSend wallet](./dapp.mdx) will automatically read this query parameter, and consider -itself connected as a result of a claim redirect. - -```ts -import { ZkSendLinkBuilder } from '@iota/zksend'; - -const link = new ZkSendLinkBuilder({ - sender: '0x...', - redirect: { - url: 'https://your-dapp.com', - name: 'Your dApp', - }, -}); -``` - -## Claiming a link - -To claim a link via the SDK you can use the `ZkSendLink` class: - -```ts -import { ZkSendLink } from '@iota/zksend'; - -// create a link instance from a URL -const link = await ZkSendLink.fromUrl('https://zksend.com/claim#$abc...'); - -// list what claimable assets the link has -const { nfts, balances } = link.assets; - -// claim all the assets from the link -await link.claimAssets(addressOfClaimer); -``` - -## Listing links you have created - -To list the links created by a specific address, you can use the `listCreatedLinks` function: - -```ts -import { listCreatedLinks } from '@iota/zksend'; - -const { links, hasNextPage, cursor } = await listCreatedLinks({ - address: addressOfCreator, -}); - -// get the claimable assets for this link (will be empty if the link has been claimed) -const { nfts, balances } = await links[0].assets; -``` - -## Regenerating links - -If you lose a link you've created, you can re-generate the link (this can only done from the address -that originally created the link): - -```ts -import { listCreatedLinks } from '@iota/zksend'; - -const { links, hasNextPage, cursor } = await listCreatedLinks({ - address: addressOfCreator, -}); - -// url will be the new link url -const { url, transactionBlock } = await links[0].link.createRegenerateTransaction( - addressOfLinkCreator, -); - -// Execute the transaction block to regenerate the link -await client.signAndExecuteTransactionBlock({ - transactionBlock, - signer: keypair, -}); -``` - -## Bulk link creation - -To create multiple links in a single transaction block, you can use `ZkSendLinkBuilder.createLinks`: - -```ts -const links = []; - -for (let i = 0; i < 10; i++) { - const link = new ZkSendLinkBuilder({ - client, - sender: keypair.toIotaAddress(), - }); - - link.addClaimableMicros(100n); - links.push(link); -} - -const urls = links.map((link) => link.getLink()); - -const txb = await ZkSendLinkBuilder.createLinks({ - links, -}); - -await client.signAndExecuteTransactionBlock({ - transactionBlock: txb, - signer: keypair, -}); -``` diff --git a/sdk/docs/typedoc.json b/sdk/docs/typedoc.json index 044c6ad4f1c..d444630da81 100644 --- a/sdk/docs/typedoc.json +++ b/sdk/docs/typedoc.json @@ -7,8 +7,7 @@ "../graphql-transport", "../kiosk", "../typescript", - "../zklogin", - "../zksend" + "../zklogin" ], "excludeInternal": true, "excludePrivate": true, diff --git a/sdk/zksend/.prettierignore b/sdk/zksend/.prettierignore deleted file mode 100644 index f44447b8ce9..00000000000 --- a/sdk/zksend/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -CHANGELOG.md -dist/ diff --git a/sdk/zksend/CHANGELOG.md b/sdk/zksend/CHANGELOG.md deleted file mode 100644 index e7a4752dccf..00000000000 --- a/sdk/zksend/CHANGELOG.md +++ /dev/null @@ -1,125 +0,0 @@ -# @iota/zksend - -## 0.4.1 - -### Patch Changes - -- Updated dependencies [6984dd1e38] - - @iota/iota.js@0.51.1 - - @iota/wallet-standard@0.11.1 - -## 0.4.0 - -### Minor Changes - -- c05a4e8cb7: removed listClaimableAssets, and added new assets and claimed properties to link instances -- c05a4e8cb7: Use contract by default for new links -- c05a4e8cb7: Add helper for bulk link creation -- c05a4e8cb7: Removed options for filtering claims -- c05a4e8cb7: renamed loadOwnedData to loadAssets - -## 0.3.1 - -### Patch Changes - -- b82832279b: Add isClaimTransaction helper - -## 0.3.0 - -### Minor Changes - -- 3b1da3967a: Add support for contract based links - -## 0.2.3 - -### Patch Changes - -- Updated dependencies [0cafa94027] -- Updated dependencies [437f0ca2ef] - - @iota/iota.js@0.51.0 - - @iota/wallet-standard@0.11.0 - -## 0.2.2 - -### Patch Changes - -- 4830361fa4: Updated typescript version -- 4fd676671b: Fix issue with overwriting balances when adding multiple balances for the same unnormalized coinType" -- Updated dependencies [4830361fa4] - - @iota/wallet-standard@0.10.3 - - @iota/iota.js@0.50.1 - -## 0.2.1 - -### Patch Changes - -- f069e3a13d: fix listing assets for empty links - -## 0.2.0 - -### Minor Changes - -- e81f49e8dc: Add SDK for creating ZKSend links - -### Patch Changes - -- c07aa19958: Fix coin merging for sending balances -- 13e922d9b1: Rework timing and window opening logic to try and improve browser compatibility -- c859f41a1c: Handle base64 with spaces in hash -- d21c01ed47: Add method for claiming zksend assets from link -- 2814db6529: Fix required redirect -- e87d99734a: Add method for sending non-iota balances -- ba6fccd010: Add support for autoconnection from redirects -- c6b3066069: Fix cursor when enumerating links owned assets -- 66fbbc7faa: Detect gasCoin when claiming -- 7b8d044603: Detect wallet closing -- c6b3066069: Improve zkSend error messages -- a2904e0075: Fix for claimable assets not accounting for cases where claimable balance comes from gas coin -- ea2744b0c3: Add redirect parameter and fix listing assets on links without Iota -- 44a1f9ea0b: Tweak types of events sent over the bridge -- 7cc09a7bb4: Handle cases where list of objects to transfer is empty -- 9a14e61db4: Add gas estimation for creating zksend links -- f041b10b9f: Allow origin to be set when registering zksend wallet" -- c1f6cfff47: Fix import paths -- 7c9a8cc24b: Fix window opening for transactions with unresolved data -- ae9ae17eea: Fix ownedAfterClaim check -- Updated dependencies [a34f1cb67d] -- Updated dependencies [c08e3569ef] -- Updated dependencies [9a14e61db4] -- Updated dependencies [13e922d9b1] -- Updated dependencies [a34f1cb67d] -- Updated dependencies [220a766d86] - - @iota/iota.js@0.50.0 - - @iota/wallet-standard@0.10.2 - -## 0.1.1 - -### Patch Changes - -- Updated dependencies [9ac0a4ec01] - - @iota/wallet-standard@0.10.1 - -## 0.1.0 - -### Minor Changes - -- e5f9e3ba21: Replace tsup based build to fix issues with esm/cjs dual publishing - -### Patch Changes - -- Updated dependencies [e5f9e3ba21] - - @iota/wallet-standard@0.10.0 - -## 0.0.3 - -### Patch Changes - -- Updated dependencies [dd362ec1d6] -- Updated dependencies [165ad6b21d] - - @iota/wallet-standard@0.9.0 - -## 0.0.2 - -### Patch Changes - -- @iota/wallet-standard@0.8.11 diff --git a/sdk/zksend/README.md b/sdk/zksend/README.md deleted file mode 100644 index bcc4d938265..00000000000 --- a/sdk/zksend/README.md +++ /dev/null @@ -1 +0,0 @@ -# `@iota/zksend` diff --git a/sdk/zksend/package.json b/sdk/zksend/package.json deleted file mode 100644 index 52d6e4aa22d..00000000000 --- a/sdk/zksend/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "@iota/zksend", - "version": "0.4.1", - "description": "TODO: Write Description", - "license": "Apache-2.0", - "author": "Mysten Labs ", - "type": "commonjs", - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", - "types": "./dist/cjs/index.d.ts", - "exports": { - ".": { - "import": "./dist/esm/index.js", - "require": "./dist/cjs/index.js" - } - }, - "sideEffects": false, - "files": [ - "CHANGELOG.md", - "dist" - ], - "scripts": { - "clean": "rm -rf tsconfig.tsbuildinfo ./dist", - "build": "build-package", - "prepublishOnly": "pnpm build", - "prettier:check": "prettier -c --ignore-unknown .", - "prettier:fix": "prettier -w --ignore-unknown .", - "eslint:check": "eslint --max-warnings=0 .", - "eslint:fix": "pnpm run eslint:check --fix", - "lint": "pnpm run eslint:check && pnpm run prettier:check", - "lint:fix": "pnpm run eslint:fix && pnpm run prettier:fix" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/iotaledger/iota.git" - }, - "bugs": { - "url": "https://github.com/iotaledger/iota/issues" - }, - "homepage": "https://github.com/iotaledger/iota#readme", - "devDependencies": { - "@iota/build-scripts": "workspace:*", - "@types/node": "^20.4.2", - "typescript": "^5.3.3", - "vitest": "^0.33.0" - }, - "dependencies": { - "@iota/iota.js": "workspace:*", - "@iota/wallet-standard": "workspace:*", - "mitt": "^3.0.1", - "nanostores": "^0.9.3", - "valibot": "^0.25.0" - } -} diff --git a/sdk/zksend/src/channel/events.ts b/sdk/zksend/src/channel/events.ts deleted file mode 100644 index 9cc1c69c2e9..00000000000 --- a/sdk/zksend/src/channel/events.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import type { Output } from 'valibot'; -import { literal, object, optional, string, url, uuid, variant } from 'valibot'; - -export const ZkSendRequestData = variant('type', [ - object({ - type: literal('connect'), - }), - object({ - type: literal('sign-transaction-block'), - data: string('`data` is required'), - address: string('`address` is required'), - }), - object({ - type: literal('sign-personal-message'), - bytes: string('`bytes` is required'), - address: string('`address` is required'), - }), -]); -export type ZkSendRequestData = Output; - -export const ZkSendRequest = object({ - id: string('`id` is required', [uuid()]), - origin: string([url('`origin` must be a valid URL')]), - name: optional(string()), - payload: ZkSendRequestData, -}); - -export type ZkSendRequest = Output; - -export const ZkSendResponseData = variant('type', [ - object({ - type: literal('connect'), - address: string(), - }), - object({ - type: literal('sign-transaction-block'), - bytes: string(), - signature: string(), - }), - object({ - type: literal('sign-personal-message'), - bytes: string(), - signature: string(), - }), -]); -export type ZkSendResponseData = Output; - -export const ZkSendResponsePayload = variant('type', [ - object({ - type: literal('reject'), - }), - object({ - type: literal('resolve'), - data: ZkSendResponseData, - }), -]); -export type ZkSendResponsePayload = Output; - -export const ZkSendResponse = object({ - id: string([uuid()]), - source: literal('zksend-channel'), - payload: ZkSendResponsePayload, -}); -export type ZkSendResponse = Output; - -export type ZkSendRequestTypes = Record & { - [P in ZkSendRequestData as P['type']]: P; -}; - -export type ZkSendResponseTypes = { - [P in ZkSendResponseData as P['type']]: P; -}; diff --git a/sdk/zksend/src/channel/index.ts b/sdk/zksend/src/channel/index.ts deleted file mode 100644 index 31bedaecd50..00000000000 --- a/sdk/zksend/src/channel/index.ts +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import type { Output } from 'valibot'; -import { parse, safeParse } from 'valibot'; - -import { withResolvers } from '../utils/withResolvers.js'; -import type { ZkSendRequestData, ZkSendResponsePayload, ZkSendResponseTypes } from './events.js'; -import { ZkSendRequest, ZkSendResponse } from './events.js'; - -export const DEFAULT_ZKSEND_ORIGIN = 'https://zksend.com'; - -export { ZkSendRequest, ZkSendResponse }; - -interface ZkSendPopupOptions { - origin?: string; - name: string; -} - -export class ZkSendPopup { - #id: string; - #origin: string; - #name: string; - - #close?: () => void; - - constructor({ origin = DEFAULT_ZKSEND_ORIGIN, name }: ZkSendPopupOptions) { - this.#id = crypto.randomUUID(); - this.#origin = origin; - this.#name = name; - } - - async createRequest( - request: T, - ): Promise { - const popup = window.open('about:blank', '_blank'); - - if (!popup) { - throw new Error('Failed to open new window'); - } - - const { promise, resolve, reject } = withResolvers(); - - let interval: NodeJS.Timer | null = null; - - function cleanup() { - if (interval) { - clearInterval(interval); - } - window.removeEventListener('message', listener); - } - - const listener = (event: MessageEvent) => { - if (event.origin !== this.#origin) { - return; - } - const { success, output } = safeParse(ZkSendResponse, event.data); - if (!success || output.id !== this.#id) return; - - cleanup(); - - if (output.payload.type === 'reject') { - reject(new Error('User rejected the request')); - } else if (output.payload.type === 'resolve') { - resolve(output.payload.data as ZkSendResponseTypes[T['type']]); - } - }; - - this.#close = () => { - cleanup(); - popup?.close(); - }; - - window.addEventListener('message', listener); - - const { type, ...data } = request; - - popup?.location.assign( - `${this.#origin}/dapp/${type}?${new URLSearchParams({ - id: this.#id, - origin: window.origin, - name: this.#name, - })}${data ? `#${new URLSearchParams(data as Record)}` : ''}`, - ); - - interval = setInterval(() => { - try { - if (popup?.closed) { - cleanup(); - reject(new Error('User closed the zkSend window')); - } - } catch { - // This can error during the login flow, but that's fine. - } - }, 1000); - - return promise; - } - - close() { - this.#close?.(); - } -} - -export class ZkSendHost { - #request: Output; - - constructor(request: Output) { - if (typeof window === 'undefined' || !window.opener) { - throw new Error( - 'ZkSendHost can only be used in a window opened through `window.open`. `window.opener` is not available.', - ); - } - - this.#request = request; - } - - static fromUrl(url: string = window.location.href) { - const parsed = new URL(url); - - const urlHashData = parsed.hash - ? Object.fromEntries( - [...new URLSearchParams(parsed.hash.slice(1))].map(([key, value]) => [ - key, - value.replace(/ /g, '+'), - ]), - ) - : {}; - - const request = parse(ZkSendRequest, { - id: parsed.searchParams.get('id'), - origin: parsed.searchParams.get('origin'), - name: parsed.searchParams.get('name'), - payload: { - type: parsed.pathname.split('/').pop(), - ...urlHashData, - }, - }); - - return new ZkSendHost(request); - } - - getRequestData() { - return this.#request; - } - - sendMessage(payload: ZkSendResponsePayload) { - window.opener.postMessage( - { - id: this.#request.id, - source: 'zksend-channel', - payload, - } satisfies ZkSendResponse, - this.#request.origin, - ); - } - - close(payload?: ZkSendResponsePayload) { - if (payload) { - this.sendMessage(payload); - } - window.close(); - } -} diff --git a/sdk/zksend/src/index.test.ts b/sdk/zksend/src/index.test.ts deleted file mode 100644 index 0b5a2b64e74..00000000000 --- a/sdk/zksend/src/index.test.ts +++ /dev/null @@ -1,483 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { getFullnodeUrl, IotaClient, IotaObjectChange } from '@iota/iota.js/client'; -import { decodeIotaPrivateKey } from '@iota/iota.js/cryptography'; -// import { getFaucetHost, requestIotaFromFaucetV0 } from '@iota/iota.js/faucet'; -import { Ed25519Keypair } from '@iota/iota.js/keypairs/ed25519'; -import { TransactionBlock } from '@iota/iota.js/transactions'; -import { toB64 } from '@iota/iota.js/utils'; -import { describe } from 'node:test'; -import { expect, test } from 'vitest'; - -import { ZkSendLink, ZkSendLinkBuilder } from './index.js'; -import { listCreatedLinks } from './links/list-created-links.js'; - -export const DEMO_BEAR_CONFIG = { - packageId: '0xab8ed19f16874f9b8b66b0b6e325ee064848b1a7fdcb1c2f0478b17ad8574e65', - type: '0xab8ed19f16874f9b8b66b0b6e325ee064848b1a7fdcb1c2f0478b17ad8574e65::demo_bear::DemoBear', -}; - -export const ZK_BAG_CONFIG = { - packageId: '0x036fee67274d0d85c3532f58296abe0dee86b93864f1b2b9074be6adb388f138', - bagStoreId: '0x5c63e71734c82c48a3cb9124c54001d1a09736cfb1668b3b30cd92a96dd4d0ce', - bagStoreTableId: '0x4e1bc4085d64005e03eb4eab2510d527aeba9548cda431cb8f149ff37451f870', -}; - -const client = new IotaClient({ - url: getFullnodeUrl('testnet'), -}); - -// 0x6e43d0e58341db532a87a16aaa079ae6eb1ed3ae8b77fdfa4870a268ea5d5db8 -const keypair = Ed25519Keypair.fromSecretKey( - decodeIotaPrivateKey('iotaprivkey1qrlgsqryjmmt59nw7a76myeeadxrs3esp8ap2074qz8xaq5kens32f7e3u7') - .secretKey, -); - -// Automatically get gas from testnet is not working reliably, manually request gas via discord, -// or uncomment the beforeAll and gas function below -// beforeAll(async () => { -// await getIotaFromFaucet(keypair); -// }); - -// async function getIotaFromFaucet(keypair: Keypair) { -// const faucetHost = getFaucetHost('testnet'); -// const result = await requestIotaFromFaucetV0({ -// host: faucetHost, -// recipient: keypair.toIotaAddress(), -// }); - -// if (result.error) { -// throw new Error(result.error); -// } -// } - -describe('Contract links', () => { - test( - 'create and claim link', - async () => { - const link = new ZkSendLinkBuilder({ - client, - contract: ZK_BAG_CONFIG, - sender: keypair.toIotaAddress(), - }); - - const bears = await createBears(3); - - for (const bear of bears) { - link.addClaimableObject(bear.objectId); - } - - link.addClaimableMicros(100n); - - const linkUrl = link.getLink(); - - await link.create({ - signer: keypair, - waitForTransactionBlock: true, - }); - - const claimLink = await ZkSendLink.fromUrl(linkUrl, { - contract: ZK_BAG_CONFIG, - network: 'testnet', - claimApi: 'https://zksend-git-mh-contract-claims-mysten-labs.vercel.app/api', - }); - - const claimableAssets = claimLink.assets!; - - expect(claimLink.claimed).toEqual(false); - expect(claimableAssets.nfts.length).toEqual(3); - expect(claimableAssets.balances).toMatchInlineSnapshot(` - [ - { - "amount": 100n, - "coinType": "0x0000000000000000000000000000000000000000000000000000000000000002::iota::IOTA", - }, - ] - `); - - const claim = await claimLink.claimAssets(keypair.toIotaAddress()); - - const res = await client.waitForTransactionBlock({ - digest: claim.digest, - options: { - showObjectChanges: true, - }, - }); - - expect(res.objectChanges?.length).toEqual( - 3 + // bears, - 1 + // coin - 1 + // gas - 1, // bag - ); - - const link2 = await ZkSendLink.fromUrl(linkUrl, { - contract: ZK_BAG_CONFIG, - network: 'testnet', - claimApi: 'https://zksend-git-mh-contract-claims-mysten-labs.vercel.app/api', - }); - expect(link2.assets?.balances).toEqual(claimLink.assets?.balances); - expect(link2.assets?.nfts.map((nft) => nft.objectId)).toEqual( - claimLink.assets?.nfts.map((nft) => nft.objectId), - ); - expect(link2.claimed).toEqual(true); - }, - { - timeout: 30_000, - }, - ); - - test( - 'regenerate links', - async () => { - const linkKp = new Ed25519Keypair(); - const link = new ZkSendLinkBuilder({ - keypair: linkKp, - client, - contract: ZK_BAG_CONFIG, - sender: keypair.toIotaAddress(), - }); - - const bears = await createBears(3); - - for (const bear of bears) { - link.addClaimableObject(bear.objectId); - } - - link.addClaimableMicros(100n); - - await link.create({ - signer: keypair, - waitForTransactionBlock: true, - }); - - const { - links: [lostLink], - } = await listCreatedLinks({ - address: keypair.toIotaAddress(), - network: 'testnet', - contract: ZK_BAG_CONFIG, - }); - - const { url, transactionBlock } = await lostLink.link.createRegenerateTransaction( - keypair.toIotaAddress(), - ); - - const result = await client.signAndExecuteTransactionBlock({ - transactionBlock, - signer: keypair, - options: { - showEffects: true, - showObjectChanges: true, - }, - }); - - await client.waitForTransactionBlock({ digest: result.digest }); - - const claimLink = await ZkSendLink.fromUrl(url, { - contract: ZK_BAG_CONFIG, - network: 'testnet', - claimApi: 'https://zksend-git-mh-contract-claims-mysten-labs.vercel.app/api', - }); - - expect(claimLink.assets?.nfts.length).toEqual(3); - expect(claimLink.assets?.balances).toMatchInlineSnapshot(` - [ - { - "amount": 100n, - "coinType": "0x0000000000000000000000000000000000000000000000000000000000000002::iota::IOTA", - }, - ] - `); - - const claim = await claimLink.claimAssets(keypair.toIotaAddress()); - - const res = await client.waitForTransactionBlock({ - digest: claim.digest, - options: { - showObjectChanges: true, - }, - }); - - expect(res.objectChanges?.length).toEqual( - 3 + // bears, - 1 + // coin - 1 + // gas - 1, // bag - ); - const link2 = await ZkSendLink.fromUrl(url, { - contract: ZK_BAG_CONFIG, - network: 'testnet', - claimApi: 'https://zksend-git-mh-contract-claims-mysten-labs.vercel.app/api', - }); - expect(link2.assets?.balances).toEqual(claimLink.assets?.balances); - expect(link2.assets?.nfts.map((nft) => nft.objectId)).toEqual( - claimLink.assets?.nfts.map((nft) => nft.objectId), - ); - expect(link2.claimed).toEqual(true); - }, - { - timeout: 30_000, - }, - ); - - test( - 'bulk link creation', - async () => { - const bears = await createBears(3); - - const links = []; - for (const bear of bears) { - const link = new ZkSendLinkBuilder({ - client, - contract: ZK_BAG_CONFIG, - sender: keypair.toIotaAddress(), - }); - - link.addClaimableMicros(100n); - link.addClaimableObject(bear.objectId); - - links.push(link); - } - - const txb = await ZkSendLinkBuilder.createLinks({ - links, - client, - contract: ZK_BAG_CONFIG, - }); - - const result = await client.signAndExecuteTransactionBlock({ - transactionBlock: txb, - signer: keypair, - }); - - await client.waitForTransactionBlock({ digest: result.digest }); - - for (const link of links) { - const linkUrl = link.getLink(); - - const claimLink = await ZkSendLink.fromUrl(linkUrl, { - contract: ZK_BAG_CONFIG, - network: 'testnet', - claimApi: 'https://zksend-git-mh-contract-claims-mysten-labs.vercel.app/api', - }); - - const claimableAssets = claimLink.assets!; - - expect(claimLink.claimed).toEqual(false); - expect(claimableAssets.nfts.length).toEqual(1); - expect(claimableAssets.balances).toMatchInlineSnapshot(` - [ - { - "amount": 100n, - "coinType": "0x0000000000000000000000000000000000000000000000000000000000000002::iota::IOTA", - }, - ] - `); - - const claim = await claimLink.claimAssets(keypair.toIotaAddress()); - - const res = await client.waitForTransactionBlock({ - digest: claim.digest, - options: { - showObjectChanges: true, - }, - }); - - expect(res.objectChanges?.length).toEqual( - 1 + // bears, - 1 + // coin - 1 + // gas - 1, // bag - ); - } - }, - { - timeout: 60_000, - }, - ); -}); - -describe('Non contract links', () => { - test( - 'Links with separate gas coin', - async () => { - const link = new ZkSendLinkBuilder({ - client, - sender: keypair.toIotaAddress(), - contract: null, - }); - - const bears = await createBears(3); - - for (const bear of bears) { - link.addClaimableObject(bear.objectId); - } - - link.addClaimableMicros(100n); - - const linkUrl = link.getLink(); - - await link.create({ - signer: keypair, - waitForTransactionBlock: true, - }); - - // Balances sometimes not updated even though we wait for the transaction to be indexed - await new Promise((resolve) => setTimeout(resolve, 3000)); - - const claimLink = await ZkSendLink.fromUrl(linkUrl, { - contract: ZK_BAG_CONFIG, - network: 'testnet', - }); - - expect(claimLink.assets?.nfts.length).toEqual(3); - expect(claimLink.assets?.balances).toMatchInlineSnapshot(` - [ - { - "amount": 100n, - "coinType": "0x0000000000000000000000000000000000000000000000000000000000000002::iota::IOTA", - }, - ] - `); - - const claimTx = await claimLink.claimAssets(new Ed25519Keypair().toIotaAddress()); - - const res = await client.waitForTransactionBlock({ - digest: claimTx.digest, - options: { - showObjectChanges: true, - }, - }); - - expect(res.objectChanges?.length).toEqual( - 3 + // bears, - 1 + // coin - 1, // gas - ); - - const link2 = await ZkSendLink.fromUrl(linkUrl, { - contract: ZK_BAG_CONFIG, - network: 'testnet', - claimApi: 'https://zksend-git-mh-contract-claims-mysten-labs.vercel.app/api', - }); - expect(link2.assets?.balances).toEqual(claimLink.assets?.balances); - expect(link2.assets?.nfts.map((nft) => nft.objectId)).toEqual( - claimLink.assets?.nfts.map((nft) => nft.objectId), - ); - expect(link2.claimed).toEqual(true); - }, - { - timeout: 30_000, - }, - ); - - test( - 'Links with single coin', - async () => { - const linkKp = new Ed25519Keypair(); - - const txb = new TransactionBlock(); - - const [coin] = txb.splitCoins(txb.gas, [5_000_000]); - txb.transferObjects([coin], linkKp.toIotaAddress()); - - const { digest } = await client.signAndExecuteTransactionBlock({ - signer: keypair, - transactionBlock: txb, - }); - - await client.waitForTransactionBlock({ digest }); - - const claimLink = new ZkSendLink({ - keypair: linkKp, - network: 'testnet', - isContractLink: false, - }); - - await claimLink.loadAssets(); - - expect(claimLink.assets?.nfts.length).toEqual(0); - expect(claimLink.assets?.balances.length).toEqual(1); - expect(claimLink.assets?.balances[0].coinType).toEqual( - '0x0000000000000000000000000000000000000000000000000000000000000002::iota::IOTA', - ); - - const claimTx = await claimLink.claimAssets(keypair.toIotaAddress()); - - const res = await client.waitForTransactionBlock({ - digest: claimTx.digest, - options: { - showBalanceChanges: true, - }, - }); - - expect(res.balanceChanges?.length).toEqual(2); - const link2 = await ZkSendLink.fromUrl( - `https://zksend.con/claim#${toB64( - decodeIotaPrivateKey(linkKp.getSecretKey()).secretKey, - )}`, - { - contract: ZK_BAG_CONFIG, - network: 'testnet', - claimApi: 'https://zksend-git-mh-contract-claims-mysten-labs.vercel.app/api', - }, - ); - expect(link2.assets?.balances).toEqual(claimLink.assets?.balances); - expect(link2.assets?.nfts.map((nft) => nft.objectId)).toEqual( - claimLink.assets?.nfts.map((nft) => nft.objectId), - ); - expect(link2.claimed).toEqual(true); - }, - { - timeout: 30_000, - }, - ); -}); - -async function createBears(totalBears: number) { - const txb = new TransactionBlock(); - const bears = []; - - for (let i = 0; i < totalBears; i++) { - const bear = txb.moveCall({ - target: `${DEMO_BEAR_CONFIG.packageId}::demo_bear::new`, - arguments: [ - txb.pure.string(`A happy bear - ${Math.floor(Math.random() * 1_000_000_000)}`), - ], - }); - - bears.push(bear); - } - - txb.transferObjects(bears, txb.pure.address(keypair.toIotaAddress())); - - const res = await client.signAndExecuteTransactionBlock({ - transactionBlock: txb, - signer: keypair, - options: { - showObjectChanges: true, - }, - }); - - await client.waitForTransactionBlock({ - digest: res.digest, - }); - - const bearList = res - .objectChanges!.filter( - (x: IotaObjectChange) => - x.type === 'created' && x.objectType.includes(DEMO_BEAR_CONFIG.type), - ) - .map((x: IotaObjectChange) => { - if (!('objectId' in x)) throw new Error('invalid data'); - return { - objectId: x.objectId, - type: x.objectType, - }; - }); - - return bearList; -} diff --git a/sdk/zksend/src/index.ts b/sdk/zksend/src/index.ts deleted file mode 100644 index 59940531747..00000000000 --- a/sdk/zksend/src/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -export { - ZkSendLinkBuilder, - type ZkSendLinkBuilderOptions, - type CreateZkSendLinkOptions, -} from './links/builder.js'; -export { ZkSendLink, type ZkSendLinkOptions } from './links/claim.js'; -export { type ZkBagContractOptions, ZkBag } from './links/zk-bag.js'; -export { isClaimTransaction } from './links/utils.js'; -export { listCreatedLinks } from './links/list-created-links.js'; - -export { MAINNET_CONTRACT_IDS } from './links/zk-bag.js'; -export * from './wallet.js'; -export * from './channel/index.js'; diff --git a/sdk/zksend/src/links/builder.ts b/sdk/zksend/src/links/builder.ts deleted file mode 100644 index 13152e2a12e..00000000000 --- a/sdk/zksend/src/links/builder.ts +++ /dev/null @@ -1,419 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { getFullnodeUrl, IotaClient } from '@iota/iota.js/client'; -import type { CoinStruct } from '@iota/iota.js/client'; -import { decodeIotaPrivateKey } from '@iota/iota.js/cryptography'; -import type { Keypair, Signer } from '@iota/iota.js/cryptography'; -import { Ed25519Keypair } from '@iota/iota.js/keypairs/ed25519'; -import type { TransactionObjectArgument, TransactionObjectInput } from '@iota/iota.js/transactions'; -import { TransactionBlock } from '@iota/iota.js/transactions'; -import { - normalizeStructTag, - normalizeIotaAddress, - IOTA_TYPE_ARG, - toB64, -} from '@iota/iota.js/utils'; - -import type { ZkBagContractOptions } from './zk-bag.js'; -import { MAINNET_CONTRACT_IDS, ZkBag } from './zk-bag.js'; - -interface ZkSendLinkRedirect { - url: string; - name?: string; -} - -export interface ZkSendLinkBuilderOptions { - host?: string; - path?: string; - keypair?: Keypair; - network?: 'mainnet' | 'testnet'; - client?: IotaClient; - sender: string; - redirect?: ZkSendLinkRedirect; - contract?: ZkBagContractOptions | null; -} - -const DEFAULT_ZK_SEND_LINK_OPTIONS = { - host: 'https://zksend.com', - path: '/claim', - network: 'mainnet' as const, -}; - -const IOTA_COIN_TYPE = normalizeStructTag(IOTA_TYPE_ARG); - -export interface CreateZkSendLinkOptions { - transactionBlock?: TransactionBlock; - calculateGas?: (options: { - balances: Map; - objects: TransactionObjectInput[]; - gasEstimateFromDryRun: bigint; - }) => Promise | bigint; -} - -export class ZkSendLinkBuilder { - objectIds = new Set(); - balances = new Map(); - sender: string; - #host: string; - #path: string; - keypair: Keypair; - #client: IotaClient; - #redirect?: ZkSendLinkRedirect; - #coinsByType = new Map(); - #contract?: ZkBag; - - constructor({ - host = DEFAULT_ZK_SEND_LINK_OPTIONS.host, - path = DEFAULT_ZK_SEND_LINK_OPTIONS.path, - keypair = new Ed25519Keypair(), - network = DEFAULT_ZK_SEND_LINK_OPTIONS.network, - client = new IotaClient({ url: getFullnodeUrl(network) }), - sender, - redirect, - contract = network === 'mainnet' ? MAINNET_CONTRACT_IDS : undefined, - }: ZkSendLinkBuilderOptions) { - this.#host = host; - this.#path = path; - this.#redirect = redirect; - this.keypair = keypair; - this.#client = client; - this.sender = normalizeIotaAddress(sender); - - if (contract) { - this.#contract = new ZkBag(contract.packageId, contract); - } - } - - addClaimableMicros(amount: bigint) { - this.addClaimableBalance(IOTA_COIN_TYPE, amount); - } - - addClaimableBalance(coinType: string, amount: bigint) { - const normalizedType = normalizeStructTag(coinType); - this.balances.set(normalizedType, (this.balances.get(normalizedType) ?? 0n) + amount); - } - - addClaimableObject(id: string) { - this.objectIds.add(id); - } - - getLink(): string { - const link = new URL(this.#host); - link.pathname = this.#path; - link.hash = `${this.#contract ? '$' : ''}${toB64( - decodeIotaPrivateKey(this.keypair.getSecretKey()).secretKey, - )}`; - - if (this.#redirect) { - link.searchParams.set('redirect_url', this.#redirect.url); - if (this.#redirect.name) { - link.searchParams.set('name', this.#redirect.name); - } - } - - return link.toString(); - } - - async create({ - signer, - ...options - }: CreateZkSendLinkOptions & { - signer: Signer; - waitForTransactionBlock?: boolean; - }) { - const txb = await this.createSendTransaction(options); - - const result = await this.#client.signAndExecuteTransactionBlock({ - transactionBlock: await txb.build({ client: this.#client }), - signer, - }); - - if (options.waitForTransactionBlock) { - await this.#client.waitForTransactionBlock({ digest: result.digest }); - } - - return result; - } - async createSendTransaction({ - transactionBlock = new TransactionBlock(), - calculateGas, - }: CreateZkSendLinkOptions = {}) { - if (!this.#contract) { - return this.#createSendTransactionWithoutContract({ transactionBlock, calculateGas }); - } - - transactionBlock.setSenderIfNotSet(this.sender); - - return ZkSendLinkBuilder.createLinks({ - transactionBlock, - client: this.#client, - contract: this.#contract.ids, - links: [this], - }); - } - - async #objectsToTransfer(txb: TransactionBlock) { - const objectIDs = [...this.objectIds]; - const refsWithType: { - ref: TransactionObjectArgument; - type: string; - }[] = ( - await this.#client.multiGetObjects({ - ids: objectIDs, - options: { - showType: true, - }, - }) - ).map((res, i) => { - if (!res.data || res.error) { - throw new Error(`Failed to load object ${objectIDs[i]} (${res.error?.code})`); - } - - return { - ref: txb.objectRef({ - version: res.data.version, - digest: res.data.digest, - objectId: res.data.objectId, - }), - type: res.data.type!, - }; - }); - - txb.setSenderIfNotSet(this.sender); - - for (const [coinType, amount] of this.balances) { - if (coinType === IOTA_COIN_TYPE) { - const [iota] = txb.splitCoins(txb.gas, [amount]); - refsWithType.push({ - ref: iota, - type: `0x2::coin::Coin<${coinType}>`, - } as never); - } else { - const coins = (await this.#getCoinsByType(coinType)).map( - (coin) => coin.coinObjectId, - ); - - if (coins.length > 1) { - txb.mergeCoins(coins[0], coins.slice(1)); - } - const [split] = txb.splitCoins(coins[0], [amount]); - refsWithType.push({ - ref: split, - type: `0x2::coin:Coin<${coinType}>`, - }); - } - } - - return refsWithType; - } - - async #createSendTransactionWithoutContract({ - transactionBlock: txb = new TransactionBlock(), - calculateGas, - }: CreateZkSendLinkOptions = {}) { - const gasEstimateFromDryRun = await this.#estimateClaimGasFee(); - const baseGasAmount = calculateGas - ? await calculateGas({ - balances: this.balances, - objects: [...this.objectIds], - gasEstimateFromDryRun, - }) - : gasEstimateFromDryRun * 2n; - - // Ensure that rounded gas is not less than the calculated gas - const gasWithBuffer = baseGasAmount + 1013n; - // Ensure that gas amount ends in 987 - const roundedGasAmount = gasWithBuffer - (gasWithBuffer % 1000n) - 13n; - - const address = this.keypair.toIotaAddress(); - const objectsToTransfer = (await this.#objectsToTransfer(txb)).map((obj) => obj.ref); - const [gas] = txb.splitCoins(txb.gas, [roundedGasAmount]); - objectsToTransfer.push(gas); - - txb.setSenderIfNotSet(this.sender); - txb.transferObjects(objectsToTransfer, address); - - return txb; - } - - async #estimateClaimGasFee(): Promise { - const txb = new TransactionBlock(); - txb.setSender(this.sender); - txb.setGasPayment([]); - txb.transferObjects([txb.gas], this.keypair.toIotaAddress()); - - const idsToTransfer = [...this.objectIds]; - - for (const [coinType] of this.balances) { - const coins = await this.#getCoinsByType(coinType); - - if (!coins.length) { - throw new Error(`Sending account does not contain any coins of type ${coinType}`); - } - - idsToTransfer.push(coins[0].coinObjectId); - } - - if (idsToTransfer.length > 0) { - txb.transferObjects( - idsToTransfer.map((id) => txb.object(id)), - this.keypair.toIotaAddress(), - ); - } - - const result = await this.#client.dryRunTransactionBlock({ - transactionBlock: await txb.build({ client: this.#client }), - }); - - return ( - BigInt(result.effects.gasUsed.computationCost) + - BigInt(result.effects.gasUsed.storageCost) - - BigInt(result.effects.gasUsed.storageRebate) - ); - } - - async #getCoinsByType(coinType: string) { - if (this.#coinsByType.has(coinType)) { - return this.#coinsByType.get(coinType)!; - } - - const coins = await this.#client.getCoins({ - coinType, - owner: this.sender, - }); - - this.#coinsByType.set(coinType, coins.data); - - return coins.data; - } - - static async createLinks({ - links, - network = 'mainnet', - client = new IotaClient({ url: getFullnodeUrl(network) }), - transactionBlock = new TransactionBlock(), - contract: contractIds = MAINNET_CONTRACT_IDS, - }: { - transactionBlock?: TransactionBlock; - client?: IotaClient; - network?: 'mainnet' | 'testnet'; - links: ZkSendLinkBuilder[]; - contract: ZkBagContractOptions; - }) { - const contract = new ZkBag(contractIds.packageId, contractIds); - const store = transactionBlock.object(contract.ids.bagStoreId); - - const coinsByType = new Map(); - const allIds = links.flatMap((link) => [...link.objectIds]); - - await Promise.all( - [...new Set(links.flatMap((link) => [...link.balances.keys()]))].map( - async (coinType) => { - const coins = await client.getCoins({ - coinType, - owner: links[0].sender, - }); - - coinsByType.set( - coinType, - coins.data.filter((coin) => !allIds.includes(coin.coinObjectId)), - ); - }, - ), - ); - - const objectRefs = new Map< - string, - { - ref: TransactionObjectArgument; - type: string; - } - >(); - - const pageSize = 50; - let offset = 0; - while (offset < allIds.length) { - const chunk = allIds.slice(offset, offset + pageSize); - offset += pageSize; - - const objects = await client.multiGetObjects({ - ids: chunk, - options: { - showType: true, - }, - }); - - for (const [i, res] of objects.entries()) { - if (!res.data || res.error) { - throw new Error(`Failed to load object ${chunk[i]} (${res.error?.code})`); - } - objectRefs.set(chunk[i], { - ref: transactionBlock.objectRef({ - version: res.data.version, - digest: res.data.digest, - objectId: res.data.objectId, - }), - type: res.data.type!, - }); - } - } - - const mergedCoins = new Map([ - [IOTA_COIN_TYPE, transactionBlock.gas], - ]); - - for (const [coinType, coins] of coinsByType) { - if (coinType === IOTA_COIN_TYPE) { - continue; - } - - const [first, ...rest] = coins.map((coin) => - transactionBlock.objectRef({ - objectId: coin.coinObjectId, - version: coin.version, - digest: coin.digest, - }), - ); - if (rest.length > 0) { - transactionBlock.mergeCoins(first, rest); - } - mergedCoins.set(coinType, transactionBlock.object(first)); - } - - for (const link of links) { - const receiver = link.keypair.toIotaAddress(); - contract.new(transactionBlock, { arguments: [store, receiver] }); - - link.objectIds.forEach((id) => { - const object = objectRefs.get(id); - if (!object) { - throw new Error(`Object ${id} not found`); - } - contract.add(transactionBlock, { - arguments: [store, receiver, object.ref], - typeArguments: [object.type], - }); - }); - } - - for (const [coinType, merged] of mergedCoins) { - const linksWithCoin = links.filter((link) => link.balances.has(coinType)); - if (linksWithCoin.length === 0) { - continue; - } - - const balances = linksWithCoin.map((link) => link.balances.get(coinType)!); - const splits = transactionBlock.splitCoins(merged, balances); - for (const [i, link] of linksWithCoin.entries()) { - contract.add(transactionBlock, { - arguments: [store, link.keypair.toIotaAddress(), splits[i]], - typeArguments: [`0x2::coin::Coin<${coinType}>`], - }); - } - } - - return transactionBlock; - } -} diff --git a/sdk/zksend/src/links/claim.ts b/sdk/zksend/src/links/claim.ts deleted file mode 100644 index a3c4a007608..00000000000 --- a/sdk/zksend/src/links/claim.ts +++ /dev/null @@ -1,691 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import type { PureArg } from '@iota/iota.js/bcs'; -import { bcs } from '@iota/iota.js/bcs'; -import { getFullnodeUrl, IotaClient } from '@iota/iota.js/client'; -import type { CoinStruct, IotaTransaction } from '@iota/iota.js/client'; -import type { Keypair } from '@iota/iota.js/cryptography'; -import { Ed25519Keypair } from '@iota/iota.js/keypairs/ed25519'; -import { TransactionBlock } from '@iota/iota.js/transactions'; -import { - fromB64, - normalizeStructTag, - normalizeIotaAddress, - normalizeIotaObjectId, - parseStructTag, - IOTA_TYPE_ARG, - toB64, -} from '@iota/iota.js/utils'; - -import type { ZkSendLinkBuilderOptions } from './builder.js'; -import { ZkSendLinkBuilder } from './builder.js'; -import type { LinkAssets } from './utils.js'; -import { getAssetsFromTxnBlock, isOwner, ownedAfterChange } from './utils.js'; -import type { ZkBagContractOptions } from './zk-bag.js'; -import { MAINNET_CONTRACT_IDS, ZkBag } from './zk-bag.js'; - -const DEFAULT_ZK_SEND_LINK_OPTIONS = { - host: 'https://zksend.com', - path: '/claim', - network: 'mainnet' as const, - claimApi: 'https://zksend.com/api', -}; - -const IOTA_COIN_TYPE = normalizeStructTag(IOTA_TYPE_ARG); -const IOTA_COIN_OBJECT_TYPE = normalizeStructTag('0x2::coin::Coin<0x2::iota::IOTA>'); - -export type ZkSendLinkOptions = { - claimApi?: string; - keypair?: Keypair; - client?: IotaClient; - network?: 'mainnet' | 'testnet'; - host?: string; - path?: string; - address?: string; - isContractLink: boolean; - contract?: ZkBagContractOptions | null; -} & ( - | { - address: string; - keypair?: never; - } - | { - keypair: Keypair; - address?: never; - } -); - -export class ZkSendLink { - address: string; - keypair?: Keypair; - creatorAddress?: string; - assets?: LinkAssets; - claimed?: boolean; - - #client: IotaClient; - #contract?: ZkBag; - #claimApi: string; - #network: 'mainnet' | 'testnet'; - #host?: string; - #path?: string; - - // State for non-contract based links - #gasCoin?: CoinStruct; - #hasIota = false; - #ownedObjects: { - objectId: string; - version: string; - digest: string; - type: string; - }[] = []; - - constructor({ - network = DEFAULT_ZK_SEND_LINK_OPTIONS.network, - claimApi = DEFAULT_ZK_SEND_LINK_OPTIONS.claimApi, - client = new IotaClient({ url: getFullnodeUrl(network) }), - keypair, - contract = network === 'mainnet' ? MAINNET_CONTRACT_IDS : null, - address, - host, - path, - isContractLink, - }: ZkSendLinkOptions) { - if (!keypair && !address) { - throw new Error('Either keypair or address must be provided'); - } - - this.#client = client; - this.keypair = keypair; - this.address = address ?? keypair!.toIotaAddress(); - this.#claimApi = claimApi; - this.#network = network; - this.#host = host; - this.#path = path; - - if (isContractLink) { - if (!contract) { - throw new Error('Contract options are required for contract based links'); - } - - this.#contract = new ZkBag(contract.packageId, contract); - } - } - - static async fromUrl( - url: string, - options: Omit = {}, - ) { - const parsed = new URL(url); - const isContractLink = parsed.hash.startsWith('#$'); - - let link: ZkSendLink; - if (isContractLink) { - const keypair = Ed25519Keypair.fromSecretKey(fromB64(parsed.hash.slice(2))); - link = new ZkSendLink({ - ...options, - keypair, - host: `${parsed.protocol}//${parsed.host}`, - path: parsed.pathname, - isContractLink: true, - }); - } else { - const keypair = Ed25519Keypair.fromSecretKey( - fromB64(isContractLink ? parsed.hash.slice(2) : parsed.hash.slice(1)), - ); - - link = new ZkSendLink({ - ...options, - keypair, - host: `${parsed.protocol}//${parsed.host}`, - path: parsed.pathname, - isContractLink: false, - }); - } - - await link.loadAssets(); - - return link; - } - - static async fromAddress( - address: string, - options: Omit, - ) { - const link = new ZkSendLink({ - ...options, - address, - isContractLink: true, - }); - - await link.loadAssets(); - - return link; - } - - async loadAssets() { - if (this.#contract) { - await this.#loadBag(); - } else { - await this.#loadOwnedObjects(); - } - } - - async claimAssets(address: string) { - if (!this.keypair) { - throw new Error('Cannot claim assets without links keypair'); - } - - if (this.claimed) { - throw new Error('Assets have already been claimed'); - } - - if (!this.#contract) { - return this.#client.signAndExecuteTransactionBlock({ - transactionBlock: this.createClaimTransaction(address), - signer: this.keypair, - }); - } - - if (!this.assets) { - await this.#loadBag(); - } - - const txb = this.createClaimTransaction(address); - - const { digest } = await this.#executeSponsoredTransactionBlock( - await this.#createSponsoredTransactionBlock(txb, address, this.keypair.toIotaAddress()), - this.keypair, - ); - - return this.#client.waitForTransactionBlock({ digest }); - } - - createClaimTransaction( - address: string, - { - reclaim, - }: { - reclaim?: boolean; - } = {}, - ) { - if (!this.#contract) { - return this.#createNonContractClaimTransaction(address); - } - - if (!this.keypair && !reclaim) { - throw new Error('Cannot claim assets without the links keypair'); - } - - const txb = new TransactionBlock(); - const sender = reclaim ? address : this.keypair!.toIotaAddress(); - txb.setSender(sender); - - const store = txb.object(this.#contract.ids.bagStoreId); - - const [bag, proof] = reclaim - ? this.#contract.reclaim(txb, { arguments: [store, this.address] }) - : this.#contract.init_claim(txb, { arguments: [store] }); - - const objectsToTransfer = []; - - const objects = [...(this.assets?.coins ?? []), ...(this.assets?.nfts ?? [])]; - - for (const object of objects) { - objectsToTransfer.push( - this.#contract.claim(txb, { - arguments: [ - bag, - proof, - txb.receivingRef({ - objectId: object.objectId, - version: object.version, - digest: object.digest, - }), - ], - typeArguments: [object.type], - }), - ); - } - - this.#contract.finalize(txb, { arguments: [bag, proof] }); - if (objectsToTransfer.length > 0) { - txb.transferObjects(objectsToTransfer, address); - } - - return txb; - } - - async createRegenerateTransaction( - sender: string, - options: Omit = {}, - ) { - if (!this.assets) { - await this.#loadBag(); - } - - if (this.claimed) { - throw new Error('Assets have already been claimed'); - } - - if (!this.#contract) { - throw new Error('Regenerating non-contract based links is not supported'); - } - - const txb = new TransactionBlock(); - txb.setSender(sender); - - const store = txb.object(this.#contract.ids.bagStoreId); - - const newLinkKp = Ed25519Keypair.generate(); - - const newLink = new ZkSendLinkBuilder({ - ...options, - sender, - client: this.#client, - contract: this.#contract.ids, - host: this.#host, - path: this.#path, - keypair: newLinkKp, - }); - - const to = txb.pure.address(newLinkKp.toIotaAddress()); - - this.#contract.update_receiver(txb, { arguments: [store, this.address, to] }); - - return { - url: newLink.getLink(), - transactionBlock: txb, - }; - } - - async #loadBag() { - if (!this.#contract) { - return; - } - - this.assets = { - balances: [], - nfts: [], - coins: [], - }; - - const bagField = await this.#client.getDynamicFieldObject({ - parentId: this.#contract.ids.bagStoreTableId, - name: { - type: 'address', - value: this.address, - }, - }); - - if (!bagField.data) { - this.claimed = true; - await this.#loadClaimedAssets(); - - return; - } else { - this.claimed = false; - } - - const itemIds: string[] | undefined = (bagField as any).data?.content?.fields?.value?.fields - ?.item_ids.fields.contents; - - this.creatorAddress = (bagField as any).data?.content?.fields?.value?.fields?.owner; - - if (!itemIds) { - throw new Error('Invalid bag field'); - } - - const objectsResponse = await this.#client.multiGetObjects({ - ids: itemIds, - options: { - showType: true, - showContent: true, - }, - }); - - const balances = new Map< - string, - { - coinType: string; - amount: bigint; - } - >(); - - objectsResponse.forEach((object, i) => { - if (!object.data || !object.data.type) { - throw new Error(`Failed to load claimable object ${itemIds[i]}`); - } - - const type = parseStructTag(normalizeStructTag(object.data.type)); - - if ( - type.address === normalizeIotaAddress('0x2') && - type.module === 'coin' && - type.name === 'Coin' - ) { - this.assets!.coins.push({ - objectId: object.data.objectId, - type: object.data.type, - version: object.data.version, - digest: object.data.digest, - }); - - if (object.data.content?.dataType === 'moveObject') { - const amount = BigInt( - (object.data.content.fields as Record).balance, - ); - const coinType = normalizeStructTag( - parseStructTag(object.data.content.type).typeParams[0], - ); - if (!balances.has(coinType)) { - balances.set(coinType, { coinType, amount }); - } else { - balances.get(coinType)!.amount += amount; - } - } - } else { - this.assets!.nfts.push({ - objectId: object.data.objectId, - type: object.data.type, - version: object.data.version, - digest: object.data.digest, - }); - } - }); - - this.assets.balances = [...balances.values()]; - } - - async #loadClaimedAssets() { - const result = await this.#client.queryTransactionBlocks({ - limit: 1, - filter: { - FromAddress: this.address, - }, - options: { - showObjectChanges: true, - showBalanceChanges: true, - showInput: true, - }, - }); - - if (!result?.data[0]) { - return; - } - - const [txb] = result.data; - - if (txb.transaction?.data.transaction.kind !== 'ProgrammableTransaction') { - return; - } - - const transfer = txb.transaction.data.transaction.transactions.findLast( - (tx): tx is Extract => - 'TransferObjects' in tx, - ); - - if (!transfer) { - return; - } - - const receiverArg = transfer.TransferObjects[1]; - - if (!(typeof receiverArg === 'object' && 'Input' in receiverArg)) { - return; - } - - const input = txb.transaction.data.transaction.inputs[receiverArg.Input]; - - if (input.type !== 'pure') { - return; - } - - const receiver = - typeof input.value === 'string' - ? input.value - : bcs.Address.parse(new Uint8Array((input.value as PureArg).Pure)); - - this.assets = getAssetsFromTxnBlock({ - transactionBlock: txb, - address: receiver, - isSent: false, - }); - } - - async #createSponsoredTransactionBlock(txb: TransactionBlock, claimer: string, sender: string) { - return this.#fetch<{ digest: string; bytes: string }>('transaction-blocks/sponsor', { - method: 'POST', - body: JSON.stringify({ - network: this.#network, - sender, - claimer, - transactionBlockKindBytes: toB64( - await txb.build({ - onlyTransactionKind: true, - client: this.#client, - // Theses limits will get verified during the final transaction construction, so we can safely ignore them here: - limits: { - maxGasObjects: Infinity, - maxPureArgumentSize: Infinity, - maxTxGas: Infinity, - maxTxSizeBytes: Infinity, - }, - }), - ), - }), - }); - } - - async #executeSponsoredTransactionBlock( - input: { digest: string; bytes: string }, - keypair: Keypair, - ) { - return this.#fetch<{ digest: string }>(`transaction-blocks/sponsor/${input.digest}`, { - method: 'POST', - body: JSON.stringify({ - signature: (await keypair.signTransactionBlock(fromB64(input.bytes))).signature, - }), - }); - } - - async #fetch(path: string, init: RequestInit): Promise { - const res = await fetch(`${this.#claimApi}/v1/${path}`, { - ...init, - headers: { - ...init.headers, - 'Content-Type': 'application/json', - }, - }); - - if (!res.ok) { - console.error(await res.text()); - throw new Error(`Request to claim API failed with status code ${res.status}`); - } - - const { data } = await res.json(); - - return data as T; - } - - async #listNonContractClaimableAssets() { - const balances: { - coinType: string; - amount: bigint; - }[] = []; - - const nfts: { - objectId: string; - type: string; - version: string; - digest: string; - }[] = []; - - const coins: { - objectId: string; - type: string; - version: string; - digest: string; - }[] = []; - - if (this.#ownedObjects.length === 0 && !this.#hasIota) { - return { - balances, - nfts, - coins, - }; - } - - const address = new Ed25519Keypair().toIotaAddress(); - const normalizedAddress = normalizeIotaAddress(address); - - const txb = this.createClaimTransaction(normalizedAddress); - - if (this.#gasCoin || !this.#hasIota) { - txb.setGasPayment([]); - } - - const dryRun = await this.#client.dryRunTransactionBlock({ - transactionBlock: await txb.build({ client: this.#client }), - }); - - dryRun.balanceChanges.forEach((balanceChange) => { - if ( - BigInt(balanceChange.amount) > 0n && - isOwner(balanceChange.owner, normalizedAddress) - ) { - balances.push({ - coinType: normalizeStructTag(balanceChange.coinType), - amount: BigInt(balanceChange.amount), - }); - } - }); - - dryRun.objectChanges.forEach((objectChange) => { - if ('objectType' in objectChange) { - const type = parseStructTag(objectChange.objectType); - - if ( - type.address === normalizeIotaAddress('0x2') && - type.module === 'coin' && - type.name === 'Coin' - ) { - if (ownedAfterChange(objectChange, normalizedAddress)) { - coins.push(objectChange); - } - return; - } - } - - if (ownedAfterChange(objectChange, normalizedAddress)) { - nfts.push(objectChange); - } - }); - - return { - balances, - nfts, - coins, - }; - } - - #createNonContractClaimTransaction(address: string) { - if (!this.keypair) { - throw new Error('Cannot claim assets without the links keypair'); - } - - const txb = new TransactionBlock(); - txb.setSender(this.keypair.toIotaAddress()); - - const objectsToTransfer = this.#ownedObjects - .filter((object) => { - if (this.#gasCoin) { - if (object.objectId === this.#gasCoin.coinObjectId) { - return false; - } - } else if (object.type === IOTA_COIN_OBJECT_TYPE) { - return false; - } - - return true; - }) - .map((object) => txb.object(object.objectId)); - - if (this.#gasCoin && this.creatorAddress) { - txb.transferObjects([txb.gas], this.creatorAddress); - } else { - objectsToTransfer.push(txb.gas); - } - - if (objectsToTransfer.length > 0) { - txb.transferObjects(objectsToTransfer, address); - } - - return txb; - } - - async #loadOwnedObjects() { - this.assets = { - nfts: [], - balances: [], - coins: [], - }; - - let nextCursor: string | null | undefined; - do { - const ownedObjects = await this.#client.getOwnedObjects({ - cursor: nextCursor, - owner: this.address, - options: { - showType: true, - showContent: true, - }, - }); - - // RPC response returns cursor even if there are no more pages - nextCursor = ownedObjects.hasNextPage ? ownedObjects.nextCursor : null; - for (const object of ownedObjects.data) { - if (object.data) { - this.#ownedObjects.push({ - objectId: normalizeIotaObjectId(object.data.objectId), - version: object.data.version, - digest: object.data.digest, - type: normalizeStructTag(object.data.type!), - }); - } - } - } while (nextCursor); - - const coins = await this.#client.getCoins({ - coinType: IOTA_COIN_TYPE, - owner: this.address, - }); - - this.#hasIota = coins.data.length > 0; - this.#gasCoin = coins.data.find((coin) => BigInt(coin.balance) % 1000n === 987n); - - const result = await this.#client.queryTransactionBlocks({ - limit: 1, - order: 'ascending', - filter: { - ToAddress: this.address, - }, - options: { - showInput: true, - showBalanceChanges: true, - showObjectChanges: true, - }, - }); - - this.creatorAddress = result.data[0]?.transaction?.data.sender; - - if (this.#hasIota || this.#ownedObjects.length > 0) { - this.claimed = false; - this.assets = await this.#listNonContractClaimableAssets(); - } else if (result.data[0]) { - this.claimed = true; - await this.#loadClaimedAssets(); - } - } -} diff --git a/sdk/zksend/src/links/list-created-links.ts b/sdk/zksend/src/links/list-created-links.ts deleted file mode 100644 index 59478e635f1..00000000000 --- a/sdk/zksend/src/links/list-created-links.ts +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { bcs } from '@iota/iota.js/bcs'; -import type { IotaClient } from '@iota/iota.js/client'; -import { IotaGraphQLClient } from '@iota/iota.js/graphql'; -import { graphql } from '@iota/iota.js/graphql/schemas/2024-01'; -import { fromB64, normalizeIotaAddress } from '@iota/iota.js/utils'; - -import { ZkSendLink } from './claim.js'; -import type { ZkBagContractOptions } from './zk-bag.js'; -import { MAINNET_CONTRACT_IDS } from './zk-bag.js'; - -const ListCreatedLinksQuery = graphql(` - query listCreatedLinks($address: IotaAddress!, $function: String!, $cursor: String) { - transactionBlocks( - last: 10 - before: $cursor - filter: { signAddress: $address, function: $function, kind: PROGRAMMABLE_TX } - ) { - pageInfo { - startCursor - hasPreviousPage - } - nodes { - effects { - timestamp - } - digest - kind { - __typename - ... on ProgrammableTransactionBlock { - inputs(first: 10) { - nodes { - __typename - ... on Pure { - bytes - } - } - } - transactions(first: 10) { - nodes { - __typename - ... on MoveCallTransaction { - module - functionName - package - arguments { - __typename - ... on Input { - ix - } - } - } - } - } - } - } - } - } - } -`); - -export async function listCreatedLinks({ - address, - cursor, - network, - contract = MAINNET_CONTRACT_IDS, - ...linkOptions -}: { - address: string; - contract?: ZkBagContractOptions; - cursor?: string; - network?: 'mainnet' | 'testnet'; - // Link options: - host?: string; - path?: string; - client?: IotaClient; -}) { - const gqlClient = new IotaGraphQLClient({ - url: - network === 'testnet' - ? 'https://iota-testnet.mystenlabs.com/graphql' - : 'https://iota-mainnet.mystenlabs.com/graphql', - }); - - const packageId = normalizeIotaAddress(contract.packageId); - - const page = await gqlClient.query({ - query: ListCreatedLinksQuery, - variables: { - address, - cursor, - function: `${packageId}::zk_bag::new`, - }, - }); - - const transactionBlocks = page.data?.transactionBlocks; - - if (!transactionBlocks || page.errors?.length) { - throw new Error('Failed to load created links'); - } - - const links = ( - await Promise.all( - transactionBlocks.nodes.map(async (node) => { - if (node.kind?.__typename !== 'ProgrammableTransactionBlock') { - throw new Error('Invalid transaction block'); - } - - const fn = node.kind.transactions.nodes.find( - (fn) => - fn.__typename === 'MoveCallTransaction' && - fn.package === packageId && - fn.module === 'zk_bag' && - fn.functionName === 'new', - ); - - if (fn?.__typename !== 'MoveCallTransaction') { - return null; - } - - const addressArg = fn.arguments[1]; - - if (addressArg.__typename !== 'Input') { - throw new Error('Invalid address argument'); - } - - const input = node.kind.inputs.nodes[addressArg.ix]; - - if (input.__typename !== 'Pure') { - throw new Error('Expected Address input to be a Pure value'); - } - - const address = bcs.Address.parse(fromB64(input.bytes as string)); - - const link = new ZkSendLink({ - network, - address, - contract, - isContractLink: true, - ...linkOptions, - }); - - await link.loadAssets(); - - return { - link, - claimed: !!link.claimed, - assets: link.assets!, - digest: node.digest, - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - createdAt: node.effects?.timestamp!, - }; - }), - ) - ).reverse(); - - return { - cursor: transactionBlocks.pageInfo.startCursor, - hasNextPage: transactionBlocks.pageInfo.hasPreviousPage, - links: links.filter((link): link is NonNullable => link !== null), - }; -} diff --git a/sdk/zksend/src/links/utils.ts b/sdk/zksend/src/links/utils.ts deleted file mode 100644 index c49ba9742d1..00000000000 --- a/sdk/zksend/src/links/utils.ts +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import type { - ObjectOwner, - IotaObjectChange, - IotaTransactionBlockResponse, -} from '@iota/iota.js/client'; -import type { TransactionBlock } from '@iota/iota.js/transactions'; -import { normalizeStructTag, normalizeIotaAddress, parseStructTag } from '@iota/iota.js/utils'; - -// eslint-disable-next-line import/no-cycle - -export interface LinkAssets { - balances: { - coinType: string; - amount: bigint; - }[]; - - nfts: { - objectId: string; - type: string; - version: string; - digest: string; - }[]; - - coins: { - objectId: string; - type: string; - version: string; - digest: string; - }[]; -} - -export function isClaimTransaction( - txb: TransactionBlock, - options: { - packageId: string; - }, -) { - let transfers = 0; - - for (const tx of txb.blockData.transactions) { - switch (tx.kind) { - case 'TransferObjects': - // Ensure that we are only transferring results of a claim - if (!tx.objects.every((o) => o.kind === 'Result' || o.kind === 'NestedResult')) { - return false; - } - transfers++; - break; - case 'MoveCall': - const [packageId, module, fn] = tx.target.split('::'); - - if (packageId !== options.packageId) { - return false; - } - - if (module !== 'zk_bag') { - return false; - } - - if ( - fn !== 'init_claim' && - fn !== 'reclaim' && - fn !== 'claim' && - fn !== 'finalize' - ) { - return false; - } - break; - default: - return false; - } - } - - return transfers === 1; -} - -export function getAssetsFromTxnBlock({ - transactionBlock, - address, - isSent, -}: { - transactionBlock: IotaTransactionBlockResponse; - address: string; - isSent: boolean; -}): LinkAssets { - const normalizedAddress = normalizeIotaAddress(address); - const balances: { - coinType: string; - amount: bigint; - }[] = []; - - const nfts: { - objectId: string; - type: string; - version: string; - digest: string; - }[] = []; - - const coins: { - objectId: string; - type: string; - version: string; - digest: string; - }[] = []; - - transactionBlock.balanceChanges?.forEach((change) => { - const validAmountChange = isSent ? BigInt(change.amount) < 0n : BigInt(change.amount) > 0n; - if (validAmountChange && isOwner(change.owner, normalizedAddress)) { - balances.push({ - coinType: normalizeStructTag(change.coinType), - amount: BigInt(change.amount), - }); - } - }); - - transactionBlock.objectChanges?.forEach((change) => { - if ('objectType' in change) { - const type = parseStructTag(change.objectType); - - if ( - type.address === normalizeIotaAddress('0x2') && - type.module === 'coin' && - type.name === 'Coin' - ) { - if ( - change.type === 'created' || - change.type === 'transferred' || - change.type === 'mutated' - ) { - coins.push(change); - } - return; - } - } - - if ( - isObjectOwner(change, normalizedAddress, isSent) && - (change.type === 'created' || - change.type === 'transferred' || - change.type === 'mutated') - ) { - nfts.push(change); - } - }); - - return { - balances, - nfts, - coins, - }; -} - -function getObjectOwnerFromObjectChange(objectChange: IotaObjectChange, isSent: boolean) { - if (isSent) { - return 'owner' in objectChange ? objectChange.owner : null; - } - - return 'recipient' in objectChange ? objectChange.recipient : null; -} - -function isObjectOwner(objectChange: IotaObjectChange, address: string, isSent: boolean) { - const owner = getObjectOwnerFromObjectChange(objectChange, isSent); - - if (isSent) { - return owner && typeof owner === 'object' && 'AddressOwner' in owner; - } - - return ownedAfterChange(objectChange, address); -} - -export function ownedAfterChange( - objectChange: IotaObjectChange, - address: string, -): objectChange is Extract { - if (objectChange.type === 'transferred' && isOwner(objectChange.recipient, address)) { - return true; - } - - if ( - (objectChange.type === 'created' || objectChange.type === 'mutated') && - isOwner(objectChange.owner, address) - ) { - return true; - } - - return false; -} - -export function isOwner(owner: ObjectOwner, address: string): owner is { AddressOwner: string } { - return ( - owner && - typeof owner === 'object' && - 'AddressOwner' in owner && - normalizeIotaAddress(owner.AddressOwner) === address - ); -} diff --git a/sdk/zksend/src/links/zk-bag.ts b/sdk/zksend/src/links/zk-bag.ts deleted file mode 100644 index 32a1bd92d57..00000000000 --- a/sdk/zksend/src/links/zk-bag.ts +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import type { - TransactionArgument, - TransactionBlock, - TransactionObjectArgument, -} from '@iota/iota.js/transactions'; - -export interface ZkBagContractOptions { - packageId: string; - bagStoreId: string; - bagStoreTableId: string; -} - -export const MAINNET_CONTRACT_IDS: ZkBagContractOptions = { - packageId: '0x5bb7d0bb3240011336ca9015f553b2646302a4f05f821160344e9ec5a988f740', - bagStoreId: '0x65b215a3f2a951c94313a89c43f0adbd2fd9ea78a0badf81e27d1c9868a8b6fe', - bagStoreTableId: '0x616db54ca564660cd58e36a4548be68b289371ef2611485c62c374a60960084e', -}; - -export class ZkBag { - #package: string; - #module = 'zk_bag' as const; - ids: IDs; - - constructor(packageAddress: string, ids: IDs) { - this.#package = packageAddress; - this.ids = ids; - } - - new( - txb: TransactionBlock, - { - arguments: [store, receiver], - }: { - arguments: [ - store: TransactionObjectArgument | string, - receiver: TransactionArgument | string, - ]; - }, - ) { - txb.moveCall({ - target: `${this.#package}::${this.#module}::new`, - arguments: [ - txb.object(store), - typeof receiver === 'string' ? txb.pure.address(receiver) : receiver, - ], - }); - } - - add( - txb: TransactionBlock, - { - arguments: [store, receiver, item], - typeArguments, - }: { - arguments: [ - store: TransactionObjectArgument | string, - receiver: TransactionArgument | string, - item: TransactionObjectArgument | string, - ]; - typeArguments: [string]; - }, - ): Extract { - return txb.moveCall({ - target: `${this.#package}::${this.#module}::add`, - arguments: [ - txb.object(store), - typeof receiver === 'string' ? txb.pure.address(receiver) : receiver, - txb.object(item), - ], - typeArguments: typeArguments, - }); - } - - init_claim( - txb: TransactionBlock, - { - arguments: [store], - }: { - arguments: [store: TransactionObjectArgument | string]; - }, - ) { - const [bag, claimProof] = txb.moveCall({ - target: `${this.#package}::${this.#module}::init_claim`, - arguments: [txb.object(store)], - }); - - return [bag, claimProof] as const; - } - - reclaim( - txb: TransactionBlock, - { - arguments: [store, receiver], - }: { - arguments: [ - store: TransactionObjectArgument | string, - receiver: TransactionArgument | string, - ]; - }, - ) { - const [bag, claimProof] = txb.moveCall({ - target: `${this.#package}::${this.#module}::reclaim`, - arguments: [ - txb.object(store), - typeof receiver === 'string' ? txb.pure.address(receiver) : receiver, - ], - }); - - return [bag, claimProof] as const; - } - - claim( - txb: TransactionBlock, - { - arguments: [bag, claim, id], - typeArguments, - }: { - arguments: [ - bag: TransactionObjectArgument | string, - claim: Extract, - id: TransactionObjectArgument | string, - ]; - typeArguments: [string]; - }, - ): Extract { - return txb.moveCall({ - target: `${this.#package}::${this.#module}::claim`, - arguments: [ - txb.object(bag), - txb.object(claim), - typeof id === 'string' ? txb.object(id) : id, - ], - typeArguments, - }); - } - - finalize( - txb: TransactionBlock, - { - arguments: [bag, claim], - }: { - arguments: [ - bag: TransactionObjectArgument | string, - claim: Extract, - ]; - }, - ) { - txb.moveCall({ - target: `${this.#package}::${this.#module}::finalize`, - arguments: [txb.object(bag), txb.object(claim)], - }); - } - - update_receiver( - txb: TransactionBlock, - { - arguments: [bag, from, to], - }: { - arguments: [ - bag: TransactionObjectArgument | string, - from: TransactionArgument | string, - to: TransactionArgument | string, - ]; - }, - ) { - txb.moveCall({ - target: `${this.#package}::${this.#module}::update_receiver`, - arguments: [ - txb.object(bag), - typeof from === 'string' ? txb.pure.address(from) : from, - typeof to === 'string' ? txb.pure.address(to) : to, - ], - }); - } -} diff --git a/sdk/zksend/src/utils/withResolvers.ts b/sdk/zksend/src/utils/withResolvers.ts deleted file mode 100644 index 9e8f4de3f28..00000000000 --- a/sdk/zksend/src/utils/withResolvers.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -export interface Resolvers { - promise: Promise; - reject: (error: Error) => void; - resolve: (value: T) => void; -} - -export function withResolvers(): Resolvers { - let resolve: (value: T) => void; - let reject: (error: Error) => void; - - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - - return { promise, reject: reject!, resolve: resolve! }; -} diff --git a/sdk/zksend/src/wallet.ts b/sdk/zksend/src/wallet.ts deleted file mode 100644 index 6169c6306f5..00000000000 --- a/sdk/zksend/src/wallet.ts +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { bcs } from '@iota/iota.js/bcs'; -import { toB64 } from '@iota/iota.js/utils'; -import type { - StandardConnectFeature, - StandardConnectMethod, - StandardDisconnectFeature, - StandardDisconnectMethod, - StandardEventsFeature, - StandardEventsListeners, - StandardEventsOnMethod, - IotaSignPersonalMessageFeature, - IotaSignPersonalMessageMethod, - IotaSignTransactionBlockFeature, - IotaSignTransactionBlockMethod, - Wallet, -} from '@iota/wallet-standard'; -import { getWallets, ReadonlyWalletAccount, SUPPORTED_CHAINS } from '@iota/wallet-standard'; -import type { Emitter } from 'mitt'; -import mitt from 'mitt'; - -import { DEFAULT_ZKSEND_ORIGIN, ZkSendPopup } from './channel/index.js'; - -type WalletEventsMap = { - [E in keyof StandardEventsListeners]: Parameters[0]; -}; - -const ZKSEND_RECENT_ADDRESS_KEY = 'zksend:recentAddress'; - -export const ZKSEND_WALLET_NAME = 'zkSend' as const; - -export class ZkSendWallet implements Wallet { - #events: Emitter; - #accounts: ReadonlyWalletAccount[]; - #origin: string; - #name: string; - - get name() { - return ZKSEND_WALLET_NAME; - } - - get icon() { - return 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjMyIiB2aWV3Qm94PSIwIDAgMzIgMzIiIHdpZHRoPSIzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGNsaXBQYXRoIGlkPSJhIj48cmVjdCBoZWlnaHQ9IjMyIiByeD0iMiIgd2lkdGg9IjMyIi8+PC9jbGlwUGF0aD48ZyBjbGlwLXBhdGg9InVybCgjYSkiPjxyZWN0IGZpbGw9IiNmZmYiIGhlaWdodD0iMzIiIHJ4PSIyIiB3aWR0aD0iMzIiLz48cGF0aCBkPSJtMCAwaDMydjMyaC0zMnoiIGZpbGw9IiNkNDA1NTEiLz48cGF0aCBkPSJtNS42NjgyNSAyNS4yNDkxYy0uNzgyNjMtLjc4MjctLjc4MDgxLTIuMDUyMS4wMDQwNi0yLjgzMjVsMTYuNjA1MjktMTYuNTEwNDdjLjc4MTctLjc3NzIzIDIuMDQ0OS0uNzc1NDMgMi44MjQzLjAwNDA0bC44Mzg3LjgzODYyYy43ODI1Ljc4MjUxLjc4MDggMi4wNTE3NC0uMDAzOCAyLjgzMjE4bC0xNi42MDE4OCAxNi41MTM4M2MtLjc4MTY1Ljc3NzUtMi4wNDUwOC43NzU4LTIuODI0NjYtLjAwMzd6bTUuNDQzMzUtMTUuOTExNjZjLTEuODA5NzIuMDUzNjctMi43NTM3MS0yLjEzMzA5LTEuNDczNDctMy40MTMzM2wuODM4MzctLjgzODMyYy4zNzUtLjM3NTA4Ljg4MzctLjU4NTc5IDEuNDE0Mi0uNTg1NzloMTMuNDc5N2MxLjEwNDYgMCAyIC44OTU0MyAyIDJ2MTMuNDc5N2MwIC41MzA1LS4yMTA3IDEuMDM5Mi0uNTg1OCAxLjQxNDJsLS44MjY5LjgyN2MtMS4yODE4IDEuMjgxOC0zLjQ3MDkuMzMzOS0zLjQxMzItMS40Nzc5bC4zMDY2LTkuNjI5OWMuMDM2Ny0xLjE1MjI3LS45MDU5LTIuMDk2OS0yLjA1ODMtMi4wNjI3M3oiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+' as const; - } - - get version() { - return '1.0.0' as const; - } - - get chains() { - return SUPPORTED_CHAINS; - } - - get accounts() { - return this.#accounts; - } - - get features(): StandardConnectFeature & - StandardDisconnectFeature & - StandardEventsFeature & - IotaSignTransactionBlockFeature & - IotaSignPersonalMessageFeature { - return { - 'standard:connect': { - version: '1.0.0', - connect: this.#connect, - }, - 'standard:disconnect': { - version: '1.0.0', - disconnect: this.#disconnect, - }, - 'standard:events': { - version: '1.0.0', - on: this.#on, - }, - 'iota:signTransactionBlock': { - version: '1.0.0', - signTransactionBlock: this.#signTransactionBlock, - }, - 'iota:signPersonalMessage': { - version: '1.0.0', - signPersonalMessage: this.#signPersonalMessage, - }, - }; - } - - constructor({ - name, - address, - origin = DEFAULT_ZKSEND_ORIGIN, - }: { - origin?: string; - address?: string | null; - name: string; - }) { - this.#accounts = []; - this.#events = mitt(); - this.#origin = origin; - this.#name = name; - - if (address) { - this.#setAccount(address); - } - } - - #signTransactionBlock: IotaSignTransactionBlockMethod = async ({ - transactionBlock, - account, - }) => { - transactionBlock.setSenderIfNotSet(account.address); - - const data = transactionBlock.serialize(); - - const popup = new ZkSendPopup({ name: this.#name, origin: this.#origin }); - const response = await popup.createRequest({ - type: 'sign-transaction-block', - data, - address: account.address, - }); - - return { - transactionBlockBytes: response.bytes, - signature: response.signature, - }; - }; - - #signPersonalMessage: IotaSignPersonalMessageMethod = async ({ message, account }) => { - const bytes = toB64(bcs.vector(bcs.u8()).serialize(message).toBytes()); - const popup = new ZkSendPopup({ name: this.#name, origin: this.#origin }); - const response = await popup.createRequest({ - type: 'sign-personal-message', - bytes, - address: account.address, - }); - - return { - bytes, - signature: response.signature, - }; - }; - - #on: StandardEventsOnMethod = (event, listener) => { - this.#events.on(event, listener); - return () => this.#events.off(event, listener); - }; - - #setAccount(address?: string) { - if (address) { - this.#accounts = [ - new ReadonlyWalletAccount({ - address, - chains: [SUPPORTED_CHAINS[0]], - features: ['iota:signTransactionBlock', 'iota:signPersonalMessage'], - // NOTE: zkSend doesn't support getting public keys, and zkLogin accounts don't have meaningful public keys anyway - publicKey: new Uint8Array(), - }), - ]; - - localStorage.setItem(ZKSEND_RECENT_ADDRESS_KEY, address); - } else { - this.#accounts = []; - } - - this.#events.emit('change', { accounts: this.accounts }); - } - - #connect: StandardConnectMethod = async (input) => { - if (input?.silent) { - const address = localStorage.getItem(ZKSEND_RECENT_ADDRESS_KEY); - - if (address) { - this.#setAccount(address); - } - - return { accounts: this.accounts }; - } - - const popup = new ZkSendPopup({ name: this.#name, origin: this.#origin }); - const response = await popup.createRequest({ - type: 'connect', - }); - if (!('address' in response)) { - throw new Error('Unexpected response'); - } - - this.#setAccount(response.address); - - return { accounts: this.accounts }; - }; - - #disconnect: StandardDisconnectMethod = async () => { - localStorage.removeItem(ZKSEND_RECENT_ADDRESS_KEY); - this.#setAccount(); - }; -} - -export function registerZkSendWallet( - name: string, - { - origin, - }: { - origin?: string; - }, -) { - const wallets = getWallets(); - - let addressFromRedirect: string | null = null; - try { - const params = new URLSearchParams(window.location.search); - addressFromRedirect = params.get('zksend_address'); - } catch { - // Ignore errors - } - - const wallet = new ZkSendWallet({ - name, - origin, - address: addressFromRedirect, - }); - - const unregister = wallets.register(wallet); - - return { - wallet, - unregister, - addressFromRedirect, - }; -} diff --git a/sdk/zksend/tsconfig.esm.json b/sdk/zksend/tsconfig.esm.json deleted file mode 100644 index df141cc11d4..00000000000 --- a/sdk/zksend/tsconfig.esm.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "ESNext", - "outDir": "dist/esm" - } -} diff --git a/sdk/zksend/tsconfig.json b/sdk/zksend/tsconfig.json deleted file mode 100644 index 27d3d39311b..00000000000 --- a/sdk/zksend/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../build-scripts/tsconfig.shared.json", - "include": ["src"], - "compilerOptions": { - "module": "CommonJS", - "outDir": "dist/cjs", - "isolatedModules": true, - "rootDir": "src" - }, - "references": [{ "path": "../wallet-standard" }, { "path": "../typescript" }] -} diff --git a/sdk/zksend/typedoc.json b/sdk/zksend/typedoc.json deleted file mode 100644 index a58ce5a936e..00000000000 --- a/sdk/zksend/typedoc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "entryPoints": ["src"], - "excludeInternal": true, - "excludePrivate": true, - "intentionallyNotExported": [] -} diff --git a/sdk/zksend/vitest.config.ts b/sdk/zksend/vitest.config.ts deleted file mode 100644 index 3804c4a05d2..00000000000 --- a/sdk/zksend/vitest.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { defineConfig } from 'vitest/config'; - -export default defineConfig({});