Skip to content

Commit

Permalink
soroban-tools#458: added new package for events provider
Browse files Browse the repository at this point in the history
  • Loading branch information
sreuland committed Apr 26, 2023
1 parent be3ffe6 commit c6ea08a
Show file tree
Hide file tree
Showing 15 changed files with 1,643 additions and 1,241 deletions.
21 changes: 21 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ yarn add @soroban-react/types
yarn add @soroban-react/freighter
yarn add @soroban-react/connect-button
yarn add @soroban-react/wallet-data
yarn add @soroban-react/events
yarn add soroban-client
```
### 2. Set your allowed chains for your Dapp
Expand Down Expand Up @@ -126,3 +127,23 @@ import { WalletData } from "@soroban-react/wallet-data";
<WalletData
sorobanContext={useSorobanReact()}>
```

## Use @soroban-react/events

Place your @soroban-react/events provider inside your @soroban-react provider. Then you can use the useSorobanEvents hook anywhere you use the react provider.

```
import {SorobanReactProvider} from '@soroban-react/core';
import {SorobanEventsProvider} from '@soroban-react/events';
...
<SorobanReactProvider
chains={chains}
appName={"Example App"}
connectors={connectors}>
<SorobanEventsProvider>
{children}
</SorobanEventsProvider>
</SorobanReactProvider>
```
2 changes: 1 addition & 1 deletion packages/chains/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
],
"dependencies": {
"@soroban-react/types": "^4.0.0",
"soroban-client": "^0.5.0"
"soroban-client": "https://github.com/stellar/js-soroban-client#main"
}
}
2 changes: 1 addition & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
],
"dependencies": {
"@soroban-react/core": "^4.1.0",
"soroban-client": "^0.5.0"
"soroban-client": "https://github.com/stellar/js-soroban-client#main"
},
"devDependencies": {
"@types/react": "^18.0.25"
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"dependencies": {
"@soroban-react/freighter": "^4.0.0",
"@soroban-react/types": "^4.0.0",
"soroban-client": "^0.5.0"
"soroban-client": "https://github.com/stellar/js-soroban-client#main"
},
"devDependencies": {
"@types/react": "^18.0.25"
Expand Down
13 changes: 13 additions & 0 deletions packages/events/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

# [4.0.0] (2023-04-25)

**Note:** First release of soroban events package





36 changes: 36 additions & 0 deletions packages/events/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# @soroban-react/events

See the official gitbook: https://soroban-react.gitbook.io/index/
___

```javascript
import { useSorobanEvents, EventSubscription } from '@soroban-react/events'

const crowdfundPledgedEventSubscription: EventSubscription = {
contractId: Constants.CrowdfundId,
topics: ['pledged_amount_changed'],
cb: (event: SorobanClient.SorobanRpc.EventResponse): void => {
let eventTokenBalance = xdr.ScVal.fromXDR(Buffer.from(event.value.xdr, 'base64'))
setTokenBalance(convert.scvalToBigNumber(eventTokenBalance))
},
id: Math.random()}

const crowdfundTargetReachedSubscription: EventSubscription = {
contractId: Constants.CrowdfundId,
topics: ['target_reached'],
cb: (event: SorobanClient.SorobanRpc.EventResponse): void => {
setTargetReached(true)
},
id: Math.random()}

const sorobanEventsContext = useSorobanEvents()
React.useEffect(() => {
const pledgedSubId = sorobanEventsContext.subscribe(crowdfundPledgedEventSubscription)
const reachedSubId = sorobanEventsContext.subscribe(crowdfundTargetReachedSubscription)

return () => {
sorobanEventsContext.unsubscribe(pledgedSubId);
sorobanEventsContext.unsubscribe(reachedSubId);
}
}, [sorobanEventsContext]);
```
42 changes: 42 additions & 0 deletions packages/events/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@soroban-react/events",
"keywords": [
"soroban-react",
"freighter",
"react",
"react-hooks",
"hooks",
"stellar",
"javascript",
"typescript",
"web3",
"context",
"frontend",
"dapp"
],
"license": "Apache-2.0",
"repository": "github:esteblock/soroban-react",
"version": "4.0.0",
"scripts": {
"prebuild": "rm -rf dist",
"build": "tsc",
"start": "tsc --watch"
},
"main": "dist/index.js",
"exports": "./dist/index.js",
"types": "dist/index.d.ts",
"files": [
"/dist"
],
"dependencies": {
"@soroban-react/core": "4.1.0",
"@soroban-react/types": "^4.0.0",
"soroban-client": "https://github.com/stellar/js-soroban-client#main"
},
"devDependencies": {
"@types/react": "^18.0.25"
},
"peerDependencies": {
"react": ">=16.8"
}
}
41 changes: 41 additions & 0 deletions packages/events/src/SorobanEventsContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, {createContext} from "react";
import * as SorobanClient from "soroban-client";
import { ChainMetadata } from "@soroban-react/types";

export type EventCallback = (event: SorobanClient.SorobanRpc.EventResponse) => void

export interface EventSubscription {
contractId: string;
topics: string[];
cb: EventCallback;
id: number;
lastLedgerStart?: number;
pagingToken?: string;
}

export interface SorobanEventsContextType {
subscribe: (subscription: EventSubscription) => number;
unsubscribe: (subscriptionId: number) => void;
subscriptions: Array<EventSubscription>;
}

export const SorobanEventsContext = createContext<SorobanEventsContextType | undefined>(undefined)

export const DefaultSorobanEventsContext: SorobanEventsContextType = {
subscriptions: [],
subscribe: (eventSubscription) => {
for (const subscription of DefaultSorobanEventsContext.subscriptions) {
if (subscription.id == eventSubscription.id) {
return eventSubscription.id
}
}
DefaultSorobanEventsContext.subscriptions.push(eventSubscription)
return eventSubscription.id
},
unsubscribe: (subscriptionId) => {
const index = DefaultSorobanEventsContext.subscriptions.findIndex((subscription) => subscription.id == subscriptionId)
if (index > -1) {
DefaultSorobanEventsContext.subscriptions.splice(index, 1);
}
}
};
96 changes: 96 additions & 0 deletions packages/events/src/SorobanEventsProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, {useEffect, useRef} from 'react';
import * as SorobanClient from 'soroban-client';
import { useSorobanReact, SorobanContextType } from '@soroban-react/core';
import { SorobanEventsContext, DefaultSorobanEventsContext} from "./SorobanEventsContext";;
let xdr = SorobanClient.xdr

export interface SorobanEventsProviderProps {
children: React.ReactNode;
}

export function SorobanEventsProvider({
children,
}: SorobanEventsProviderProps) {
const pollInterval = 5000
const sorobanContext = useSorobanReact()

useEffect(() => {
let timeoutId: NodeJS.Timer | null = null
let stop = false

async function pollEvents(): Promise<void> {
try {

if (!sorobanContext.server) {
console.debug("RPC events: Not connected to server")
return
}

for (const subscription of DefaultSorobanEventsContext.subscriptions) {
if (!subscription.lastLedgerStart) {
let latestLedgerState = await sorobanContext.server.getLatestLedger();
subscription.lastLedgerStart = latestLedgerState.sequence
}

const subscriptionTopicXdrs: Array<string> = []
subscription.topics && subscription.topics.forEach( topic => {
subscriptionTopicXdrs.push( xdr.ScVal.scvSymbol(topic).toXDR("base64"));
})

// TODO: updated js-soroban-client to include latestLedger
interface GetEventsWithLatestLedger extends SorobanClient.SorobanRpc.GetEventsResponse {
latestLedger?: string;
}

// TODO: use rpc batch for single round trip, each subscription can be one
// getEvents request in the batch, is that possible now?
let response = await sorobanContext.server.getEvents({
startLedger: !subscription.pagingToken ? subscription.lastLedgerStart : undefined,
cursor: subscription.pagingToken,
filters: [
{
contractIds: [subscription.contractId],
topics: [subscriptionTopicXdrs],
type: "contract"
}
],
limit: 10}) as GetEventsWithLatestLedger;

delete subscription.pagingToken;
if (response.latestLedger) {
subscription.lastLedgerStart = parseInt(response.latestLedger);
}
response.events && response.events.forEach(event => {
try {
subscription.cb(event)
} catch (error) {
console.error("Poll Events: subscription callback had error: ", error);
} finally {
subscription.pagingToken = event.pagingToken
}
})
}
} catch (error) {
console.error("Poll Events: error: ", error);
} finally {
if (!stop) {
timeoutId = setTimeout(pollEvents, pollInterval);
}
}
}

pollEvents();

return () => {
if (timeoutId != null) clearTimeout(timeoutId)
stop = true
}
}, [sorobanContext]);


return (
<SorobanEventsContext.Provider value = {DefaultSorobanEventsContext}>
{children}
</SorobanEventsContext.Provider>
);
}
4 changes: 4 additions & 0 deletions packages/events/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./SorobanEventsContext";
export * from "./SorobanEventsProvider";
export * from "./useSorobanEvents";

8 changes: 8 additions & 0 deletions packages/events/src/useSorobanEvents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useContext, Context } from 'react';
import { SorobanEventsContext, SorobanEventsContextType } from "./SorobanEventsContext";

export function useSorobanEvents() {
const context = useContext(SorobanEventsContext as Context<SorobanEventsContextType | undefined>)
if (!context) throw Error('useWeb3React can only be used within the Web3ReactProvider component')
return context
}
8 changes: 8 additions & 0 deletions packages/events/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"include": ["./src"],
"compilerOptions": {
"jsx": "react",
"outDir": "./dist"
}
}
3 changes: 2 additions & 1 deletion packages/wallet-data/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"dependencies": {
"@soroban-react/connect-button": "^4.1.0",
"@soroban-react/core": "^4.1.0",
"@soroban-react/types": "^4.0.0"
"@soroban-react/types": "^4.0.0",
"soroban-client": "https://github.com/stellar/js-soroban-client#main"
},
"peerDependencies": {
"react": ">=16.8"
Expand Down
14 changes: 11 additions & 3 deletions packages/wallet-data/src/useNetwork.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import React from 'react';
import { WalletChainByName } from "./provideWalletChains";
import {SorobanContextType } from "@soroban-react/core";
import { SorobanContextType } from "@soroban-react/core";
import { WalletChain } from "@soroban-react/types";
import { Server } from "soroban-client";

export function useNetwork(sorobanContext: SorobanContextType) {
const { activeChain, server } = sorobanContext
export type NetworkConfig = {
activeChain?: WalletChain;
server?: Server;
chains: Array<WalletChain>;
}

export function useNetwork(sorobanContext: SorobanContextType): NetworkConfig {
const { activeChain, server} = sorobanContext
return {
activeChain,
server,
Expand Down
Loading

0 comments on commit c6ea08a

Please sign in to comment.