diff --git a/.prettierignore b/.prettierignore index dace626..7085120 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ developers/developer-guides/vm-specific-tutorials/movevm/building-move-modules.mdx developers/developer-guides/vm-specific-tutorials/movevm/setting-up.mdx developers/developer-guides/vm-specific-tutorials/movevm/your-first-module.mdx +developers/developer-guides/integrating-initia-apps/initiadex.mdx diff --git a/developers/developer-guides/integrating-initia-apps/initiadex.mdx b/developers/developer-guides/integrating-initia-apps/initiadex.mdx index f1ef51f..6417bf5 100644 --- a/developers/developer-guides/integrating-initia-apps/initiadex.mdx +++ b/developers/developer-guides/integrating-initia-apps/initiadex.mdx @@ -694,131 +694,243 @@ import { concatBytes, toBytes } from '@noble/hashes/utils' import { toHex } from '@cosmjs/encoding' import { MsgUndelegate } from 'vm/move/msgs/staking' -const user = new Wallet( new RESTClient('http://localhost:1317', { gasPrices: -'0.015uinit', gasAdjustment: '1.75' }), new MnemonicKey({ // TODO: put your -mnemonic here mnemonic: 'mimic exist actress ...' }) ) - -const validator = new Wallet( new RESTClient('http://localhost:1317', { -gasPrices: '0.015uinit', gasAdjustment: '1.75' }), new MnemonicKey({ // TODO: -put your mnemonic here mnemonic: 'mimic exist actress ...' }) ) - -function coinMetadata(creator: string, symbol: string) { const -OBJECT_FROM_SEED_ADDRESS_SCHEME = 0xfe const addrBytes = -bcs.address().serialize(creator).toBytes() const seed = toBytes(symbol) const -bytes = new Uint8Array([...concatBytes(addrBytes, seed), -OBJECT_FROM_SEED_ADDRESS_SCHEME]) const sum = -sha3_256.create().update(bytes).digest() return toHex(sum) } - -async function getLastProposalId(restClient: RESTClient): Promise { -const [proposals, pagination] = await restClient.gov.proposals() if -(proposals.length === 0) return 0 return proposals[proposals.length - 1].id } - -async function getProposalStatus(restClient: RESTClient, proposalId: number): -Promise { const proposal = await -restClient.gov.proposal(proposalId) return proposal ? proposal.status : null } - -async function checkProposalPassed(restClient: RESTClient, proposalId: number): -Promise { for (;;) { -console.log(`checking proposal ${proposalId} status... in ${restClient.URL}/cosmos/gov/v1/proposals/${proposalId}`) -const status = await getProposalStatus(restClient, proposalId) +const user = new Wallet( + new RESTClient('http://localhost:1317', { + gasPrices: '0.015uinit', + gasAdjustment: '1.75', + }), + new MnemonicKey({ + // TODO: put your mnemonic here + mnemonic: 'mimic exist actress ...', + }), +) + +const validator = new Wallet( + new RESTClient('http://localhost:1317', { + gasPrices: '0.015uinit', + gasAdjustment: '1.75', + }), + new MnemonicKey({ + // TODO: put your mnemonic here + mnemonic: 'mimic exist actress ...', + }), +) + +function coinMetadata(creator: string, symbol: string) { + const OBJECT_FROM_SEED_ADDRESS_SCHEME = 0xfe + const addrBytes = bcs.address().serialize(creator).toBytes() + const seed = toBytes(symbol) + const bytes = new Uint8Array([ + ...concatBytes(addrBytes, seed), + OBJECT_FROM_SEED_ADDRESS_SCHEME, + ]) + const sum = sha3_256.create().update(bytes).digest() + return toHex(sum) +} + +async function getLastProposalId( + restClient: RESTClient, +): Promise { + const [proposals, pagination] = await restClient.gov.proposals() + if (proposals.length === 0) return 0 + return proposals[proposals.length - 1].id +} + +async function getProposalStatus( + restClient: RESTClient, + proposalId: number, +): Promise { + const proposal = await restClient.gov.proposal(proposalId) + return proposal ? proposal.status : null +} + +async function checkProposalPassed( + restClient: RESTClient, + proposalId: number, +): Promise { + for (;;) { + console.log( + `checking proposal ${proposalId} status... in ${restClient.URL}/cosmos/gov/v1/proposals/${proposalId}`, + ) + const status = await getProposalStatus(restClient, proposalId) if (status === ProposalStatus.PROPOSAL_STATUS_PASSED) return - if (status === ProposalStatus.PROPOSAL_STATUS_REJECTED) throw new Error(`proposal ${proposalId} rejected`) - if (status === ProposalStatus.PROPOSAL_STATUS_FAILED) throw new Error(`proposal ${proposalId} failed`) + if (status === ProposalStatus.PROPOSAL_STATUS_REJECTED) + throw new Error(`proposal ${proposalId} rejected`) + if (status === ProposalStatus.PROPOSAL_STATUS_FAILED) + throw new Error(`proposal ${proposalId} failed`) await delay(5_000) - -} } - -async function provideLiquidity( lp_metadata: string, coin_a_amount: number, -coin_b_amount: number, min_liquidity: number | null ) { const msg = new -MsgExecute( user.key.accAddress, '0x1', 'dex', 'provide_liquidity_script', [], [ -bcs.string().serialize(lp_metadata).toBase64(), -bcs.u64().serialize(coin_a_amount).toBase64(), -bcs.u64().serialize(coin_b_amount).toBase64(), -bcs.option(bcs.u64()).serialize(min_liquidity).toBase64() ] ) - -const signedTx = await user.createAndSignTx({ msgs: [msg] }) await -user.rest.tx.broadcast(signedTx).catch(console.log) } - -async function createPairScript( sender: Wallet, name: string, symbol: string, -swap_fee_rate: number, coin_a_weight: number, coin_b_weight: number, -coin_a_metadata: string, coin_b_metadata: string, coin_a_amount: number, -coin_b_amount: number ) { const msg = new MsgExecute( sender.key.accAddress, -'0x1', 'dex', 'create_pair_script', [], [ -bcs.string().serialize(name).toBase64(), -bcs.string().serialize(symbol).toBase64(), -bcs.bigdecimal().serialize(swap_fee_rate).toBase64(), -bcs.bigdecimal().serialize(coin_a_weight).toBase64(), -bcs.bigdecimal().serialize(coin_b_weight).toBase64(), -bcs.object().serialize(coin_a_metadata).toBase64(), -bcs.object().serialize(coin_b_metadata).toBase64(), -bcs.u64().serialize(coin_a_amount).toBase64(), -bcs.u64().serialize(coin_b_amount).toBase64() ] ) - -const signedTx = await sender.createAndSignTx({ msgs: [msg] }) await -sender.rest.tx.broadcast(signedTx).catch(console.log) } - -async function whitelistLP(lpMetadata: string) { const msgWhiteList = new -MsgWhitelist( 'init10d07y265gmmuvt4z0w9aw880jnsr700j55nka3', // authority -AccAddress.fromHex(lpMetadata), // metadata '1000000000000000000' // weight for -reward (10^18) ) - -const proposal = new MsgSubmitProposal( [msgWhiteList], '100000000uinit', // -deposit user.key.accAddress, // proposer 'uinit', // metadata 'awesome -proposal', // title 'it is awesome', // summary false // expedited ) - -const proposalId = (await getLastProposalId(user.rest)) + 1 - -// if there's only one validator, it will exceed the quorum (cause we set as -user = validator) const vote = new MsgVote(proposalId, user.key.accAddress, -VoteOption.VOTE_OPTION_YES, '') const signedTx = await user.createAndSignTx({ -msgs: [proposal, vote]}) - -await user.rest.tx.broadcast(signedTx).catch(console.log) await -checkProposalPassed(user.rest, proposalId) } - -async function delegateLP( lpMetadata: string, amount: number ) { // we can get -lp denom from lp metadata by adding 'move/' prefix // if lp metadata is -ff5c7412979... // then lp denom is move/ff5c7412979... const msg = new -MsgDelegate( user.key.accAddress, // delegator validator.key.valAddress, // -validator `${amount}move/${lpMetadata}` // lp token ) - -const signedTx = await user.createAndSignTx({ msgs: [msg] }) await -user.rest.tx.broadcast(signedTx).catch(console.log) } - -async function withdrawRewards() { const msg = new MsgWithdrawDelegatorReward( -user.key.accAddress, validator.key.valAddress ) - -const signedTx = await user.createAndSignTx({ msgs: [msg] }) await -user.rest.tx.broadcast(signedTx).catch(console.log } - -// NOTE: if you uncomment step 2, there will be an error // because it takes a -bit of time to create a pair async function main() { console.log('user:', -user.key.accAddress) console.log('validator:', validator.key.valAddress) - -// step 1: create pair script await createPairScript( user, 'init-usdc', -'init-usdc', 0.003, 0.5, 0.5, coinMetadata('0x1', 'uinit'), coinMetadata('0x1', -'uusdc'), 100_000_000, 100_000_000 ) - -const lpMetadata = coinMetadata(user.key.accAddress, 'init-usdc') // -ff5c7412979176c5e7f084a6... console.log('step 1 done, lp metadata:', lpMetadata) - -// step 2 (optional): provide liquidity // you will get LP tokens when you -create a pair, so you can skip this step // await provideLiquidity( // -lpMetadata, // 100_000_000, // 100_000_000, // 100_000 // ) // console.log('step -2 provide liquidity done') - -// step 3: whitelist LP // this step could take a while (check your -'expedited_voting_period' time in genesis.json) await whitelistLP(lpMetadata) -console.log('step 3 whitelist done') - -// step 4: delegate LP tokens to the validator await delegateLP(lpMetadata, -100_000) console.log('step 4 delegate done') - -// step 5: withdraw rewards // await withdrawRewards() // console.log('step 5 -withdraw done') } - -if (require.main === module) { main() } + } +} + +async function provideLiquidity( + lp_metadata: string, + coin_a_amount: number, + coin_b_amount: number, + min_liquidity: number | null, +) { + const msg = new MsgExecute( + user.key.accAddress, + '0x1', + 'dex', + 'provide_liquidity_script', + [], + [ + bcs.string().serialize(lp_metadata).toBase64(), + bcs.u64().serialize(coin_a_amount).toBase64(), + bcs.u64().serialize(coin_b_amount).toBase64(), + bcs.option(bcs.u64()).serialize(min_liquidity).toBase64(), + ], + ) + + const signedTx = await user.createAndSignTx({ msgs: [msg] }) + await user.rest.tx.broadcast(signedTx).catch(console.log) +} + +async function createPairScript( + sender: Wallet, + name: string, + symbol: string, + swap_fee_rate: number, + coin_a_weight: number, + coin_b_weight: number, + coin_a_metadata: string, + coin_b_metadata: string, + coin_a_amount: number, + coin_b_amount: number, +) { + const msg = new MsgExecute( + sender.key.accAddress, + '0x1', + 'dex', + 'create_pair_script', + [], + [ + bcs.string().serialize(name).toBase64(), + bcs.string().serialize(symbol).toBase64(), + bcs.bigdecimal().serialize(swap_fee_rate).toBase64(), + bcs.bigdecimal().serialize(coin_a_weight).toBase64(), + bcs.bigdecimal().serialize(coin_b_weight).toBase64(), + bcs.object().serialize(coin_a_metadata).toBase64(), + bcs.object().serialize(coin_b_metadata).toBase64(), + bcs.u64().serialize(coin_a_amount).toBase64(), + bcs.u64().serialize(coin_b_amount).toBase64(), + ], + ) + + const signedTx = await sender.createAndSignTx({ msgs: [msg] }) + await sender.rest.tx.broadcast(signedTx).catch(console.log) +} + +async function whitelistLP(lpMetadata: string) { + const msgWhiteList = new MsgWhitelist( + 'init10d07y265gmmuvt4z0w9aw880jnsr700j55nka3', // authority + AccAddress.fromHex(lpMetadata), // metadata + '1000000000000000000', // weight for reward (10^18) + ) + + const proposal = new MsgSubmitProposal( + [msgWhiteList], + '100000000uinit', // deposit + user.key.accAddress, // proposer + 'uinit', // metadata + 'awesome proposal', // title + 'it is awesome', // summary + false, // expedited + ) + + const proposalId = (await getLastProposalId(user.rest)) + 1 + + // if there's only one validator, it will exceed the quorum (cause we set as + // user = validator) + const vote = new MsgVote( + proposalId, + user.key.accAddress, + VoteOption.VOTE_OPTION_YES, + '', + ) + const signedTx = await user.createAndSignTx({ msgs: [proposal, vote] }) + + await user.rest.tx.broadcast(signedTx).catch(console.log) + await checkProposalPassed(user.rest, proposalId) +} + +async function delegateLP(lpMetadata: string, amount: number) { + // we can get lp denom from lp metadata by adding 'move/' prefix + // if lp metadata is ff5c7412979... + // then lp denom is move/ff5c7412979... + const msg = new MsgDelegate( + user.key.accAddress, // delegator + validator.key.valAddress, // validator + `${amount}move/${lpMetadata}`, // lp token + ) + + const signedTx = await user.createAndSignTx({ msgs: [msg] }) + await user.rest.tx.broadcast(signedTx).catch(console.log) +} + +async function withdrawRewards() { + const msg = new MsgWithdrawDelegatorReward( + user.key.accAddress, + validator.key.valAddress, + ) + + const signedTx = await user.createAndSignTx({ msgs: [msg] }) + await user.rest.tx.broadcast(signedTx).catch(console.log) +} + +// NOTE: if you uncomment step 2, there will be an error +// because it takes a bit of time to create a pair +async function main() { + console.log('user:', user.key.accAddress) + console.log('validator:', validator.key.valAddress) + + // step 1: create pair script + await createPairScript( + user, + 'init-usdc', + 'init-usdc', + 0.003, + 0.5, + 0.5, + coinMetadata('0x1', 'uinit'), + coinMetadata('0x1', 'uusdc'), + 100_000_000, + 100_000_000, + ) + + const lpMetadata = coinMetadata(user.key.accAddress, 'init-usdc') // + // ff5c7412979176c5e7f084a6... + console.log('step 1 done, lp metadata:', lpMetadata) + + // step 2 (optional): provide liquidity + // you will get LP tokens when you create a pair, so you can skip this step + // await provideLiquidity( + // lpMetadata, + // 100_000_000, + // 100_000_000, + // 100_000 + // ) + // console.log('step 2 provide liquidity done') + + // step 3: whitelist LP + // this step could take a while (check your 'expedited_voting_period' time in + // genesis.json) + await whitelistLP(lpMetadata) + console.log('step 3 whitelist done') + + // step 4: delegate LP tokens to the validator + await delegateLP(lpMetadata, 100_000) + console.log('step 4 delegate done') + + // step 5: withdraw rewards + // await withdrawRewards() + // console.log('step 5 withdraw done') +} + +if (require.main === module) { + main() +} ``` diff --git a/docs.json b/docs.json index 76427c6..75450c6 100644 --- a/docs.json +++ b/docs.json @@ -398,6 +398,21 @@ "interwovenkit/integrations/rainbowkit" ] }, + { + "group": "Features", + "pages": [ + { + "group": "Autosign", + "icon": "wand-sparkles", + "pages": [ + "interwovenkit/features/autosign/introduction", + "interwovenkit/features/autosign/configuration", + "interwovenkit/features/autosign/usage", + "interwovenkit/features/autosign/api-reference" + ] + } + ] + }, { "group": "References", "pages": [ diff --git a/interwovenkit/features/autosign/api-reference.mdx b/interwovenkit/features/autosign/api-reference.mdx new file mode 100644 index 0000000..c899301 --- /dev/null +++ b/interwovenkit/features/autosign/api-reference.mdx @@ -0,0 +1,213 @@ +--- +title: API Reference +description: Complete API documentation for the autosign feature +icon: code +--- + +## Overview + +The `autoSign` object provides methods and properties for managing autosign +functionality. It is available via the `useInterwovenKit()` hook. + +## Accessing the API + +```tsx +import { useInterwovenKit } from '@initia/interwovenkit-react' + +function MyComponent() { + const { autoSign } = useInterwovenKit() + + // Use autoSign methods and properties +} +``` + +## Type Definitions + +### AutoSign Object + +```tsx +interface AutoSign { + isLoading: boolean + enable: (chainId?: string) => Promise + disable: (chainId?: string) => Promise + expiration: Date | null + expirations: Record +} +``` + +## Properties + +### isLoading + +Indicates whether autosign status is currently being checked. + + + Returns `true` when autosign status is being initialized or checked, `false` + otherwise. Use this to show loading indicators in your UI. + + +**Example:** + +```tsx +function AutosignButton() { + const { autoSign } = useInterwovenKit() + + if (autoSign.isLoading) { + return + } + + return +} +``` + +### expiration + +Expiration date for autosign on the default chain. + + + Returns a `Date` object representing when autosign expires on the default + chain, or `null` if autosign is not enabled. Use this to display expiration + information and check if autosign is currently active. + + +**Example:** + +```tsx +function ExpirationDisplay() { + const { autoSign } = useInterwovenKit() + + if (autoSign.expiration && autoSign.expiration > new Date()) { + return

Autosign expires: {autoSign.expiration.toLocaleString()}

+ } + + return

Autosign is not enabled

+} +``` + +### expirations + +Map of chain IDs to expiration timestamps for all configured chains. + + + Returns an object mapping chain IDs to expiration timestamps (in milliseconds + since epoch). Use this for multi-chain applications to check autosign status + across multiple chains. Returns `null` for chains where autosign is not + enabled. + + +**Example:** + +```tsx +function MultiChainStatus() { + const { autoSign } = useInterwovenKit() + + return ( +
+ {Object.entries(autoSign.expirations).map(([chainId, timestamp]) => { + if (!timestamp) return null + + const expiration = new Date(timestamp) + const isActive = expiration > new Date() + + return ( +
+ {chainId}: {isActive ? 'Active' : 'Expired'} + {isActive && ` (until ${expiration.toLocaleString()})`} +
+ ) + })} +
+ ) +} +``` + +## Methods + +### enable() + +Enables autosign for a specific chain or the default chain. + + + Opens a drawer for user confirmation and creates the necessary authz and + feegrant permissions. Returns a Promise that resolves when autosign is + successfully enabled or rejects if the user cancels or an error occurs. + +**Parameters:** - `chainId` (optional): Chain ID to enable autosign for. If not +provided, uses the default chain ID from `InterwovenKitProvider`. + +**Returns:** Promise that resolves when autosign is enabled + +**Throws:** Error if user rejects, permissions are not configured, or autosign +is already enabled + + + +**Example:** + +```tsx +async function enableAutosign() { + try { + await autoSign.enable() + console.log('Autosign enabled for default chain') + } catch (error) { + console.error('Failed to enable autosign:', error) + } +} + +async function enableForChain() { + try { + await autoSign.enable('minievm-2') + console.log('Autosign enabled for minievm-2') + } catch (error) { + console.error('Failed to enable autosign:', error) + } +} +``` + +**Error Cases:** + +- `"User rejected auto sign setup"`: User canceled the confirmation dialog +- `"Auto sign permissions are not configured"`: `enableAutoSign` is not + configured in the provider +- `"Auto sign is already enabled"`: Autosign is already active for the specified + chain ID + +### disable() + +Disables autosign for a specific chain or the default chain. + + + Revokes all authz and feegrant permissions for the specified chain, preventing + further automatic signing. Returns a Promise that resolves when autosign is + successfully disabled. + +**Parameters:** - `chainId` (optional): Chain ID to disable autosign for. If not +provided, uses the default chain ID from `InterwovenKitProvider`. + +**Returns:** Promise that resolves when autosign is disabled + +**Throws:** Error if permissions are not configured or autosign is not enabled + + + +**Example:** + +```tsx +async function disableAutosign() { + try { + await autoSign.disable() + console.log('Autosign disabled for default chain') + } catch (error) { + console.error('Failed to disable autosign:', error) + } +} + +async function disableForChain() { + try { + await autoSign.disable('minievm-2') + console.log('Autosign disabled for minievm-2') + } catch (error) { + console.error('Failed to disable autosign:', error) + } +} +``` diff --git a/interwovenkit/features/autosign/configuration.mdx b/interwovenkit/features/autosign/configuration.mdx new file mode 100644 index 0000000..f7646f3 --- /dev/null +++ b/interwovenkit/features/autosign/configuration.mdx @@ -0,0 +1,251 @@ +--- +title: Configuration +icon: gear +--- + +## Overview + +Configuring autosign requires two main steps: setting up Privy for embedded +wallet management and configuring autosign permissions in your +`InterwovenKitProvider`. This guide walks you through both processes. + +## Prerequisites + +Before configuring autosign, ensure you have: + +- A Privy account and application ID +- InterwovenKit already integrated into your application +- An understanding of which transaction types you need to auto-sign + +## Setting Up Privy + +Autosign requires Privy to manage embedded wallets. Privy provides secure key +management and wallet creation for ghost wallets. + + + + +1. Visit [Privy's website](https://privy.io) and create an account +2. Create a new application in the Privy dashboard +3. Copy your application ID from the dashboard + + + + + +Install the required Privy packages in your application. This guide assumes +InterwovenKit is already installed: + + +```bash npm +npm install @privy-io/react-auth +``` + +```bash yarn +yarn add @privy-io/react-auth +``` + +```bash pnpm +pnpm add @privy-io/react-auth +``` + +```bash bun +bun add @privy-io/react-auth +``` + + + + + + + +Before configuring your application, set up allowed domains in the Privy +dashboard to ensure security and proper functionality: + +1. **Access Privy Dashboard**: Log in to your Privy account +2. **Click Into Application**: Click into the application you would like Privy + to work with +3. **Navigate to Allowed Domains**: Click **App settings** on the left sidebar + and then click on the **Domains** tab +4. **Add Domains**: Under **Allowed Origins**, add: + - Your production domain (e.g., `app.example.com`) + - Your development domain (e.g., `localhost:3000`) + - Any staging domains you use + + + Privy services will only work on domains you've explicitly allowed. Add all + domains where your application will run, including localhost for development. + + + + + + +The following configuration wires Privy into InterwovenKit to enable embedded +wallets and autosign: + +1. Wrap your app with `PrivyProvider` to enable authentication and embedded + wallet management. +2. Use Privy hooks (`usePrivy`, `useLoginWithSiwe`, `useCreateWallet`, + `useWallets`) to access wallet and auth state. +3. Pass those hooks into `InterwovenKitProvider` via `privyContext`. +4. Enable autosign for supported message types using `enableAutoSign`. + +**Required Configuration for Autosign:** + +- `appId`: Your Privy application ID +- `embeddedWallets.ethereum.createOnLogin: "all-users"`: **Required for + autosign**. Ensures an embedded (ghost) wallet is created at login. Privy’s + default behavior may not create an embedded wallet automatically, which will + prevent autosign from working. + +```tsx providers.tsx +import { + PrivyProvider, + useCreateWallet, + useLoginWithSiwe, + usePrivy, + useWallets, +} from '@privy-io/react-auth' +import { + InterwovenKitProvider, + PRIVY_APP_ID, +} from '@initia/interwovenkit-react' + +function InterwovenKitWrapper({ children }) { + const privy = usePrivy() + const siwe = useLoginWithSiwe() + const { createWallet } = useCreateWallet() + const { wallets } = useWallets() + + return ( + + {children} + + ) +} + +export default function Providers() { + return ( + + {/* Your app */} + + ) +} +``` + + + + +## Configuring Autosign Permissions + +### Choose Your Autosign Mode + +Before enabling autosign, decide which mode fits your application: + +- **Boolean (`enableAutoSign`)** + - Best for simple, single-chain apps + - Automatically allows default contract execution messages for the chain + - Does **not** allow token transfers, staking, or other non-default messages + +- **Per-Chain Configuration + (`enableAutoSign: { "chain-id": ["msg.type.Url", ...] }`)** + - Required if your app sends tokens, delegates, or uses multiple chains + - You must explicitly list every allowed message type + +If your app does more than basic contract execution calls, you should skip the +boolean option and use the per-chain permissions in the +[Advanced Configuration](#advanced-configuration-explicit-permissions) section. + +Most production applications will eventually require the per-chain +configuration. + +### Simple Configuration (Limited) + +This option only enables autosign for default contract execution messages and is +intentionally restrictive. + +```tsx + + {children} + +``` + + + If you later need to support token transfers, staking, or multiple chains, you + must switch from the boolean configuration to the per-chain configuration in + the [Advanced Configuration](#advanced-configuration-explicit-permissions) + section. + + +When using boolean configuration, InterwovenKit automatically: + +- Detects your chain's VM type +- Grants permission for the appropriate message type: + - **MiniEVM**: `/minievm.evm.v1.MsgCall` + - **MiniWasm**: `/cosmwasm.wasm.v1.MsgExecuteContract` + - **MiniMove**: `/initia.move.v1.MsgExecute` + +### Advanced Configuration (Explicit Permissions) + +For multi-chain applications or when you need custom message types, specify +autosign permissions per chain: + +```tsx + + {children} + +``` + +**Configuration Format:** + +The `enableAutoSign` prop accepts an object where: + +- **Keys**: Chain IDs (strings) +- **Values**: Arrays of message type URLs (strings) + +**Common Message Types:** + +- **EVM Chains**: `/minievm.evm.v1.MsgCall` +- **Wasm Chains**: `/cosmwasm.wasm.v1.MsgExecuteContract` +- **Move Chains**: `/initia.move.v1.MsgExecute` +- **Bank Module**: `/cosmos.bank.v1beta1.MsgSend` +- **Staking**: `/cosmos.staking.v1beta1.MsgDelegate` + + + Only grant permissions for message types that your application actually needs. + Granting overly broad permissions increases security risk. + diff --git a/interwovenkit/features/autosign/introduction.mdx b/interwovenkit/features/autosign/introduction.mdx new file mode 100644 index 0000000..6ad9457 --- /dev/null +++ b/interwovenkit/features/autosign/introduction.mdx @@ -0,0 +1,116 @@ +--- +title: Introduction +description: + Learn about autosign and ghost wallets for seamless transaction signing. +icon: robot +--- + +## Overview + +Autosign enables your application to send transactions automatically without +requiring user confirmation for each transaction. This feature uses an embedded +wallet (also known as a ghost wallet) to sign specific transactions on behalf of +users, providing a seamless user experience while maintaining security through +granular permission controls. + + + Autosign is built on the Cosmos SDK's `authz` and `feegrant` modules, allowing + fine-grained control over which transaction types can be automatically signed + and when permissions expire. + + +## What Are Ghost Wallets? + +Ghost wallets are Privy-managed embedded wallets that act as authorized signers +for your application. When autosign is enabled, Privy creates a ghost wallet +that receives permission from the user's main wallet to sign specific +transaction types automatically. + +Key characteristics of ghost wallets: + +- **Separate Address**: Each ghost wallet has its own blockchain address +- **Managed by Privy**: Privy handles key management and wallet lifecycle +- **Scoped Permissions**: Can only sign transactions you've explicitly + authorized +- **Time-Limited**: Permissions expire after a set duration +- **Revocable**: Users can revoke permissions at any time + +## How Autosign Works + +When autosign is enabled, the following process occurs: + + + + +Your application requests permission to automatically sign specific transaction +types on specific chains. This is configured through the `enableAutoSign` prop +in `InterwovenKitProvider`. + + + + +Privy creates an embedded wallet (ghost wallet) that signs transactions on the +user's behalf. This wallet is created automatically when the user enables +autosign. + + + + +The user's main wallet grants permission to the ghost wallet via Cosmos SDK's +`authz` and `feegrant` modules: + +- **Authz grants**: Authorize the ghost wallet to execute specific message types +- **Feegrant**: Allows the ghost wallet to pay transaction fees on behalf of the + user + +The user signs a single transaction to create these grants. + + + + +When transactions match the granted permissions: + +1. InterwovenKit validates that the transaction message types match the grants +2. InterwovenKit checks that permissions haven't expired +3. The ghost wallet automatically signs the transaction +4. The transaction is broadcast without user interaction + + + + +## Benefits + +Autosign provides several key benefits for both users and developers: + +For users: + +- **Seamless Experience**: No need to approve every transaction manually +- **Reduced Friction**: Faster interactions, especially for frequent operations +- **Security**: Permissions are scoped, time-limited, and revocable +- **Control**: Users can see and manage all autosign permissions + +For developers: + +- **Better UX**: Reduce transaction approval fatigue +- **Flexible Permissions**: Configure exactly which transaction types can be + auto-signed +- **Multi-Chain Support**: Configure different permissions per chain +- **Trust Indicators**: Works with domain trust verification + +## Security + +Autosign maintains security through several mechanisms: + +- **Scoped Permissions**: Only specific message types can be auto-signed. For + example, you might grant permission for `/minievm.evm.v1.MsgCall` but not for + `/cosmos.bank.v1beta1.MsgSend`, ensuring the ghost wallet can only execute the + exact operations you've authorized. +- **Time-Limited Grants**: All autosign permissions have expiration dates. Users + can set expiration times when enabling autosign, and permissions automatically + expire, requiring re-authorization. +- **Domain Trust**: InterwovenKit shows security warnings for untrusted domains. + Applications listed in the Initia registry are automatically trusted, while + others show warnings that users can acknowledge or dismiss. +- **Revocable Permissions**: Users can revoke autosign permissions at any time + through their wallet settings. When revoked, all grants are immediately + invalidated. diff --git a/interwovenkit/features/autosign/usage.mdx b/interwovenkit/features/autosign/usage.mdx new file mode 100644 index 0000000..c6f5b53 --- /dev/null +++ b/interwovenkit/features/autosign/usage.mdx @@ -0,0 +1,142 @@ +--- +title: Usage +description: + Learn how to enable, disable, and manage autosign in your application +icon: play +--- + +## Overview + +Once autosign is configured, you can enable and manage it in your application +using the `autoSign` object from `useInterwovenKit()`. This guide covers how to +use autosign in your code. + +## Accessing Autosign + +The `autoSign` object is available via the `useInterwovenKit()` hook: + +```tsx +import { useInterwovenKit } from '@initia/interwovenkit-react' + +function MyComponent() { + const { autoSign } = useInterwovenKit() + + // Use autoSign.enable(), autoSign.disable(), etc. +} +``` + +## Enabling Autosign + +To enable autosign, call the `enable()` method. This opens a drawer where users +confirm the autosign setup and select an expiration duration. + +The following examples show client components for enabling and disabling +autosign. You can adapt this logic to your own UI or component structure. + +### Example: EnableAutosignButton + +```tsx +'use client' + +import { useState } from 'react' +import { useInterwovenKit } from '@initia/interwovenkit-react' + +export default function EnableAutosignButton() { + const { autoSign } = useInterwovenKit() + const [isEnabling, setIsEnabling] = useState(false) + + const handleEnable = async () => { + try { + setIsEnabling(true) + await autoSign.enable() // Uses defaultChainId + console.log('Autosign enabled successfully!') + } catch (error) { + console.error('Failed to enable autosign:', error) + } finally { + setIsEnabling(false) + } + } + + return ( + + ) +} +``` + +### Enabling for a Specific Chain + +Specify a chain ID to enable autosign for a particular chain: + +```tsx +await autoSign.enable('interwoven-1') +``` + +### What Happens When Enabled + +When `autoSign.enable()` is called: + + + + A drawer appears asking the user to confirm autosign setup, showing which permissions will be granted. + + + + The user can select an expiration duration from available defaults or use a + custom duration. + + + + Privy creates an embedded wallet if one does not exist. This happens + automatically in the background. + + + + The user signs a single transaction to grant `authz` and `feegrant` + permissions to the ghost wallet. + + + + Autosign becomes active for the specified chain and message types. The Promise resolves on success. + + + +The method returns a Promise that resolves when autosign is successfully enabled +or rejects if the user cancels or an error occurs. + +## Disabling Autosign + +Users can disable autosign at any time. This revokes all grants for the selected +chain and prevents further automatic signing. + +### Example: DisableAutosignButton + +```tsx +'use client' + +import { useInterwovenKit } from '@initia/interwovenkit-react' + +export default function DisableAutosignButton() { + const { autoSign } = useInterwovenKit() + + const handleDisable = async () => { + try { + await autoSign.disable() // Uses defaultChainId + console.log('Autosign disabled successfully!') + } catch (error) { + console.error('Failed to disable autosign:', error) + } + } + + return +} +``` + +### Disabling for a Specific Chain + +Specify a chain ID to disable autosign for a particular chain: + +```tsx +await autoSign.disable('interwoven-1') +```