Towns 백서

Towns

원문

bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 17쪽 중 4쪽

ep title="Towns Hash"> The event hash is first validated using `RiverHash` function found in `sign.go`. ```go theme={null} // sign.go func RiverHash(buffer []byte) []byte { hash := sha3.NewLegacyKeccak256() writeOrPanic(hash, HASH_HEADER) // Write length of buffer as 64-bit little endian uint. err := binary.Write(hash, binary.LittleEndian, uint64(len(buffer))) if err != nil { panic(err) } writeOrPanic(hash, HASH_SEPARATOR) writeOrPanic(hash, buffer) writeOrPanic(hash, HASH_FOOTER) return hash.Sum(nil) } ``` </Step> <Step title="Check Delegate Signature"> Once the hash is confirmed as valid for the event, the hash along with the envelope signature is used to recover the signer public key. ```go theme={null} // parsed_event.go func ParseEvent(envelope *Envelope) (*ParsedEvent, error) { ... signerPubKey, err := RecoverSignerPublicKey(hash, envelope.Signature) if err != nil { return nil, err } var streamEvent StreamEvent err = proto.Unmarshal(envelope.Event, &streamEvent) if err != nil { return nil, err } if len(streamEvent.DelegateSig) > 0 { err = CheckDelegateSig(streamEvent.CreatorAddress, signerPubKey, streamEvent.DelegateSig) if err != nil { // The old style signature is a standard ethereum message signature. // TODO(HNT-1380): once we switch to the new signing model, remove this call err2 := CheckEthereumMessageSignature(streamEvent.CreatorAddress, signerPubKey, streamEvent.DelegateSig) if err2 != nil { return nil, WrapRiverError(Err_BAD_EVENT_SIGNATURE, err).Message("Bad signature").Func("ParseEvent").Tag("error2", err2) } } } else { address := PublicKeyToAddress(signerPubKey) if !bytes.Equal(address.Bytes(), streamEvent.CreatorAddress) { return nil, RiverError(Err_BAD_EVENT_SIGNATURE, "Bad signature provided", "computed address", address, "event creatorAddress", streamEvent.CreatorAddress) } } ... ``` </Step> <Step title="Check Delegate Signature"> If the delegate signature is present on the event, the creator address is used in conjunction with the previously retrieved public key and delegate signature to prove that the signature was signed by the creator. ```go theme={null} // delegate.go func CheckDelegateSig(expectedAddress []byte, devicePubKey, delegateSig []byte) error { recoveredAddress, err := RecoverDelegateSigAddress(devicePubKey, delegateSig) if err != nil { return err } if !bytes.Equal(expectedAddress, recoveredAddress.Bytes()) { return RiverError(Err_BAD_EVENT_SIGNATURE, "Bad signature provided", "computed_address", recoveredAddress, "event_creatorAddress", expectedAddress) } return nil } ``` </Step> </Steps> <Note>Event creator wallets used to sign events are assumed to be created as Ethereum wallets. Therefore, `secp256k1` algorithm is used to validate signature and sign. Towns Stream Nodes use a hardened version of this algorithm found in [go-ethereum](https://github.com/ethereum/go-ethereum/tree/master/crypto/secp256k1) package. </Note> ### Node Delegate Events Towns Stream Nodes are created with an ECDSA wallet used for identity and so can create events destined for streams in the network just like users using their Ethereum wallet as a primary wallet or another linked wallet created using [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm). Nodes create new wallets using `go-ethereum` crypto tools in the following function. ```go theme={null} // sign.go func NewWallet(ctx context.Context) (*Wallet, error) { log := dlog.CtxLog(ctx) key, err := crypto.GenerateKey() if err != nil { return nil, err } address := crypto.PubkeyToAddress(key.PublicKey) log.Infof("New wallet generated.", "address", address.Hex(), "publicKey", crypto.FromECDSAPub(&key.PublicKey)) return &Wallet{ PrivateKeyStruct: key, PrivateKey: crypto.FromECDSA(key), Address: address, AddressStr: address.Hex(), }, nil } ``` # Getting Started Source: https://docs.towns.com/build/miniapps/getting-started To build a Mini App for Towns, follow the [Farcaster Getting Started guide](https://miniapps.farcaster.xyz/docs/getting-started). ## Sharing Mini Apps in Towns Once you've built your Mini App, you can share it in Towns in two ways: ### 1. Via Bots Bots can share Mini Apps as attachments in messages: ```typescript theme={null} await bot.sendMessage(channelId, "Check out this Mini App!", { attachments: [{ type: 'miniapp', url: 'https://your-miniapp.com' }] }) ``` ### 2. Via Direct URLs Users can paste Mini App URLs directly in Towns conversations. The Towns client will automatically render compatible Mini Apps. # Introduction Source: https://docs.towns.com/build/miniapps/introduction Mini Apps enable developers to distribute apps to Towns users without requiring app store approval. Towns follows the [Farcaster miniapp specification](https://miniapps.farcaster.xyz/), allowing developers to build interactive experiences using HTML, CSS, and JavaScript. ## What are Mini Apps? Mini Apps are web applications that run within Towns, providing users with interactive experiences directly in their conversations. They can: * Access user context and Towns-specific data * Integrate with Ethereum wallets * Compose messages back to Towns * Provide rich, interactive experiences without leaving the app ## Getting Started For a complete guide on building Mini Apps, refer to the [Farcaster Mini Apps documentation](https://miniapps.farcaster.xyz/). Towns extends the Farcaster specification with additional context data. See the [SDK](/build/miniapps/sdk) documentation for Towns-specific extensions. # SDK Source: https://docs.towns.com/build/miniapps/sdk Towns extends the Farcaster miniapp specification with additional context data specific to the Towns platform. You can find the original Farcaster miniapp specification [here](https://miniapps.farcaster.xyz/docs/specification). Towns expect you to follow the [metatags specified in the Farcaster miniapp specification](https://miniapps.farcaster.xyz/docs/specification#metatags). ## Context ### context.user Towns provides information about the currently connected user: * `username` - Username of the user. Can change per channel. * `displayName` - Display name of the user. Can change per channel. * `pfpUrl` - Profile image URL of the user. ### context.towns Towns-specific data about the current session: * `user` * `userId` - The Towns UserId of the user currently connected to the miniapp * `address` - The Towns App Smart Wallet address of the connected user * `env` - Environment identifier: `alpha` (testnet) or `omega` (production) * `spaceId` - The Space ID if the user is interacting from a space (optional) * `channelId` - The Channel ID where the user is interacting from. This can be a Space Channel, DM, or GDM. ## Actions Towns supports the following Farcaster miniapp actions: ### Core Actions * `actions.ready` - Notify when miniapp is ready * `actions.openUrl` - Open external URLs in a new window * `actions.close` - Close the miniapp * `actions.composeCast` - Compose messages in Towns with optional text and embeds * `actions.getCapabilities` - Get list of supported capabilities * `actions.getChains` - Get supported blockchain chains ### Wallet Actions * `wallet.getEthereumProvider` - Get Ethereum wallet provider for the Towns Smart Wallet * `wallet.ethProviderRequest` - Make Ethereum provider requests using the connected wallet * `wallet.eip6963RequestProvider` - Announce Ethereum provider via EIP-6963 # TownsSyncProvider Source: https://docs.towns.com/build/react-sdk/api/TownsSyncProvider Provides the sync agent to all hooks usage that interacts with the Towns Protocol. * If you want to interact with the sync agent directly, you can use the `useSyncAgent` hook. - If you want to interact with the Towns Protocol using hooks provided by this SDK, you should wrap your App with this provider. You can pass an initial sync agent instance to the provider. This can be useful for persisting authentication. ## Imports ```ts theme={null} import { TownsSyncProvider } from '@towns-protocol/react-sdk' ``` ## Definition ```ts theme={null} function TownsSyncProvider( props: { syncAgent?: SyncAgent; config?: { onTokenExpired?: () => void; }; children?: React.ReactNode; }, ): JSX.Element ``` **Source:** [TownsSyncProvider](https://github.com/towns-protocol/towns/blob/main/packages/react-sdk/src/TownsSyncProvider.tsx) ## Parameters ### props * **Type:** `{ syncAgent?: SyncAgent; config?: { onTokenExpired?: () => void; }; children?: React.ReactNode; }` The props for the provider ## Return Type The provider ```ts theme={null} JSX.Element ``` # connectTowns Source: https://docs.towns.com/build/react-sdk/api/connectTowns Connect to Towns using a SignerContext Useful for server side code, allowing you to persist the signer context and use it to auth with Towns later ## Imports ```ts theme={null} import { connectTowns } from '@towns-protocol/react-sdk' ``` ## Definition ```ts theme={null} function connectTowns( signerContext: SignerContext, config: Omit<SyncAgentConfig, "context">, ): Promise<SyncAgent> ``` **Source:** [connectTowns](https://github.com/towns-protocol/towns/blob/main/packages/react-sdk/src/connectTowns.ts) ## Parameters ### signerContext * **Type:** `SignerContext` The signer context to use ### config * **Type:** `Omit<SyncAgentConfig, "context">` The configuration for the sync agent ## Return Type The sync agent ```ts theme={null} Promise<SyncAgent> ``` # connectTownsWithBearerToken Source: https://docs.towns.com/build/react-sdk/api/connectTownsWithBearerToken Connect to Towns using a Bearer Token Towns clients can use this to connect to Towns Protocol on behalf of a user Useful for server side code, allowing you to persist the signer context and use it to auth with Towns later ## Imports ```ts theme={null} import { connectTownsWithBearerToken } from '@towns-protocol/react-sdk' ``` ## Definition ```ts theme={null} function connectTownsWithBearerToken( token: string, config: Omit<SyncAgentConfig, "context">, ): Promise<SyncAgent> ``` **Source:** [connectTownsWithBearerToken](https://github.com/towns-protocol/towns/blob/main/packages/react-sdk/src/connectTowns.ts) ## Parameters ### token * **Type:** `string` The bearer token to use ### config * **Type:** `Omit<SyncAgentConfig, "context">` The configuration for the sync agent ## Return Type The sync agent ```ts theme={null} Promise<SyncAgent> ``` # getRoom Source: https://docs.towns.com/build/react-sdk/api/getRoom ## Imports ```ts theme={null} import { getRoom } from '@towns-protocol/react-sdk' ``` ## Definition ```ts theme={null} function getRoom( sync: SyncAgent, streamId: string, ): Gdm | Channel | Dm | Space ``` **Source:** [getRoom](https://github.com/towns-protocol/towns/blob/main/packages/react-sdk/src/utils.ts) ## Parameters ### sync * **Type:** `SyncAgent` ### streamId * **Type:** `string` ## Return Type ```ts theme={null} Gdm | Channel | Dm | Space ``` # signAndConnect Source: https://docs.towns.com/build/react-sdk/api/signAndConnect Sign and connect to Towns using a Signer and a random delegate wallet every time ## Imports ```ts theme={null} import { signAndConnect } from '@towns-protocol/react-sdk' ``` ## Definition ```ts theme={null} function signAndConnect( signer: ethers.Signer, config: Omit<SyncAgentConfig, "context">, ): Promise<SyncAgent> ``` **Source:** [signAndConnect](https://github.com/towns-protocol/towns/blob/main/packages/react-sdk/src/connectTowns.ts) ## Parameters ### signer * **Type:** `ethers.Signer` The signer to use ### config * **Type:** `Omit<SyncAgentConfig, "context">` The configuration for the sync agent ## Return Type The sync agent ```ts theme={null} Promise<SyncAgent> ``` # useAdminRedact Source: https://docs.towns.com/build/react-sdk/api/useAdminRedact Hook to redact any message in a channel if you're an admin. ## Imports ```ts theme={null} import { useAdminRedact } from '@towns-protocol/react-sdk' ``` ## Examples ### Redact a message You can use `adminRedact` to redact a message in a stream. ```ts theme={null} import { useAdminRedact } from '@towns-protocol/react-sdk' const { adminRedact } = useAdminRedact(streamId) adminRedact({ eventId: messageEventId }) ``` ### Redact a message reaction You can also use `redact` to redact a message reaction in a stream. ```ts theme={null} import { useRedact } from '@towns-protocol/react-sdk' const { redact } = useRedact(streamId) redact({ eventId: reactionEventId }) ``` ## Definition ```ts theme={null} function useAdminRedact( streamId: string, config?: ActionConfig<Channel["adminRedact"]>, ): { data: { eventId: string; } | undefined; error: Error | undefined; isPending: boolean; isSuccess: boolean; isError: boolean; adminRedact: (eventId: string) => Promise<{ eventId: string; }>; } ``` **Source:** [useAdminRedact](https://github.com/towns-protocol/towns/blob/main/packages/react-sdk/src/useAdminRedact.ts) ## Parameters ### streamId * **Type:** `string` The id of the stream to redact the message in. ### config * **Type:** `ActionConfig<Channel["adminRedact"]>` * **Optional** Configuration options for the action. ## Return Type The `redact` action and its loading state. ```ts theme={null} { data: { eventId: string; } | undefined; error: Error | undefined; isPending: boolean; isSuccess: boolean; isError: boolean; adminRedact: (eventId: string) => Promise<{ eventId: string; }>; } ``` # useAgentConnection Source: https://docs.towns.com/build/react-sdk/api/useAgentConnection Hook for managing the connection to the sync agent ## Imports ```ts theme={null} import { useAgentConnection } from '@towns-protocol/react-sdk' ``` ## Examples You can connect the Sync Agent to Towns Protocol using a Bearer Token or using a Signer. ### Bearer Token ```tsx theme={null} import { useAgentConnection } from '@towns-protocol/react-sdk' import { townsEnv } from '@towns-protocol/sdk' import { useState } from 'react' const townsConfig = townsEnv().makeTownsConfig('beta') const Login = () => { const { connectUsingBearerToken, isAgentConnecting, isAgentConnected } = useAgentConnection() const [bearerToken, setBearerToken] = useState('') return ( <> <input value={bearerToken} onChange={(e) => setBearerToken(e.target.value)} /> <button onClick={() => connectUsingBearerToken(bearerToken, { townsConfig })}> Login </button> {isAgentConnecting && <span>Connecting... ⏳</span>} {isAgentConnected && <span>Connected ✅</span>} </> ) } ``` ### Signer If you're using Wagmi and Viem, you can use the [`useEthersSigner`](https://wagmi.sh/react/guides/ethers#usage-1) hook to get an ethers.js v5 Signer from a Viem Wallet Client. ```tsx theme={null} import { useAgentConnection } from '@towns-protocol/react-sdk' import { townsEnv } from '@towns-protocol/sdk' import { useEthersSigner } from './utils/viem-to-ethers' const townsConfig = townsEnv().makeTownsConfig('beta') const Login = () => { const { connect, isAgentConnecting, isAgentConnected } = useAgentConnection() const signer = useEthersSigner() return ( <> <button onClick={async () => { if (!signer) { return } connect(signer, { townsConfig }) }}> Login </button> {isAgentConnecting && <span>Connecting... ⏳</span>} {isAgentConnected && <span>Connected ✅</span>} </> ) } ``` ## Definition ```ts theme={null} function useAgentConnection(): { connect: (signer: ethers.Signer, config: AgentConnectConfig) => Promise<SyncAgent | undefined>; connectUsingBearerToken: (bearerToken: string, config: AgentConnectConfig) => Promise<SyncAgent | undefined>; disconnect: () => void | undefined; isAgentConnecting: boolean; isAgentConnected: boolean; env: string | undefined; } ``` **Source:** [useAgentConnection](https://github.com/towns-protocol/towns/blob/main/packages/react-sdk/src/useAgentConnection.ts) ## Return Type The connection state and methods (connect, connectUsingBearerToken, disconnect) ```ts theme={null} { connect: (signer: ethers.Signer, config: AgentConnectConfig) => Promise<SyncAgent | undefined>; connectUsingBearerToken: (bearerToken: string, config: AgentConnectConfig) => Promise<SyncAgent | undefined>; disconnect: () => void | undefined; isAgentConnecting: boolean; isAgentConnected: boolean; env: string | undefined; } ``` # useChannel Source: https://docs.towns.com/build/react-sdk/api/useChannel Hook to get data about a channel. You can use this hook to get channel metadata and if the user has joined the channel. ## Imports ```ts theme={null} import { useChannel } from '@towns-protocol/react-sdk' ``` ## Definition ```ts theme={null} function useChannel( spaceId: string, channelId: string, config?: ObservableConfig.FromObservable<Channel>, ): ObservableValue<ChannelModel> ``` **Source:** [useChannel](https://github.com/towns-protocol/towns/blob/main/packages/react-sdk/src/useChannel.ts) ## Parameters ### spaceId * **Type:** `string` The id of the space the channel belongs to. ### channelId * **Type:** `string` The id of the channel to get data about. ### config * **Type:** `ObservableConfig.FromObservable<Channel>` * **Optional** Configuration options for the observable. ## Return Type The ChannelModel data. ```ts theme={null} ObservableValue<ChannelModel> ``` # useCreateChannel Source: https://docs.towns.com/build/react-sdk/api/useCreateChannel Hook to create a channel. ## Imports ```ts theme={null} import { useCreateChannel } from '@towns-protocol/react-sdk' ``` ## Definition ```ts theme={null} function useCreateChannel( spaceId: string, config?: ActionConfig<Space["createChannel"]>, ): { data: string | undefined; error: Error | undefined; isPending: boolean; isSuccess: boolean; isError: boolean; createChannel: (channelName: string, signer: Signer, opts?: { topic?: string; } | undefined) => Promise<string>; } ``` **Source:** [useCreateChannel](https://github.com/towns-protocol/towns/blob/main/packages/react-sdk/src/useCreateChannel.ts) ## Parameters ### spaceId * **Type:** `string` ### config * **Type:** `ActionConfig<Space["createChannel"]>` * **Optional** Configuration options for the action. ## Return Type The `createChannel` action and its loading state. ```ts theme={null} { data: string | undefined; error: Error | undefined; isPending: boolean; isSuccess: boolean; isError: boolean; createChannel: (channelName: string, signer: Signer, opts?: { topic?: string; } | undefined) => Promise<string>; } ``` # useCreateDm Source: https://docs.towns.com/build/react-sdk/api/useCreateDm A hook that allows you to create a new direct message (DM). ## Imports ```ts theme={null} import { useCreateDm } from '@towns-protocol/react-sdk' ``` ## Definition ```ts theme={null} function useCreateDm( config?: ActionConfig<Dms["createDM"]>, ): { data: { streamId: string; } | undefined; error: Error | undefined; isPending: boolean; isSuccess: boolean; isError: boolean; createDM: (userId: string, appAddress?: string | undefined, streamSettings?: { disableMiniblockCreation: boolean; lightStream: boolean; } | undefined) => Promise<{ streamId: string; }>; } ``` **Source:** [useCreateDm](https://github.com/towns-protocol/towns/blob/main/packages/react-sdk/src/useCreateDm.ts) ## Parameters ### config * **Type:** `ActionConfig<Dms["createDM"]>` * **Optional** The action config. ## Return Type An object containing the `createDM` action and the rest of the action result. ```ts theme={null} { data: { streamId: string; } | undefined; error: Error | undefined; isPending: boolean; isSuccess: boolean; isError: boolean; createDM: (userId: string, appAddress?: string | undefined, streamSettings?: { disableMiniblockCreation: boolean; lightStream: boolean; } | undefined) => Promise<{ streamId: string; }>; } ``` # useCreateGdm Source: https://docs.towns.com/
프로젝트로 돌아가기