Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ALL-2835 - Extension Ecosystem enabler #933

Merged
merged 11 commits into from
Sep 22, 2023
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## [3.1.8] - 2023.09.22
### Added
- Capability to initialize `TatumSDK` with various extensions from Extension Ecosystem using the init() method and `TatumConfig.configureExtensions`.
- Encapsulation of `typedi` functionalities within `TatumSdkContainer`, ensuring that there are no enforced dependencies on the extensions.
- `TatumSdkExtension` abstract class that allows extension creators to utilize services or other extensions from withing the `TatumSdkContainer`.
- `async init()` method call for each registered extension
- `destroy()` method call for each registered extension

## [3.1.7] - 2023.09.22
### Fixed
- Fixed 'tokenType' field to 'type' in Non-Fungible Token (NFT) Data Transfer Object (DTO) to match the API response.
Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,33 @@ Explore various applications that utilize the Tatum SDK. These examples help ill
- [Metamask portfolio](https://github.com/tatumio/example-apps/tree/master/Metamask/portfolio)
- [Metamask transfer](https://github.com/tatumio/example-apps/tree/master/Metamask/transfer)

## Extension Ecosystem

### Quickstart

Import any Tatum SDK extension to your project and start using it right away.

```typescript
import { TatumSDK, Ethereum, Network, ApiVersion } from '@tatumio/tatum'
import { HelloWorldExtension } from "@tatumio/hello-world"

const tatumSdk = await TatumSDK.init<Ethereum>({
Smrecz marked this conversation as resolved.
Show resolved Hide resolved
network: Network.ETHEREUM_SEPOLIA,
configureExtensions: [
HelloWorldExtension,
]
})
```

After that you can use the extension in your code with full intellisense.

```typescript
await tatumSdk.extension(HelloWorldExtension).sayHello()
````


Learn more about Tatum SDK Extension ecosystem here - [Tatum SDK Extensions](https://github.com/tatumio/ecosystem-addons)
samuelsramko marked this conversation as resolved.
Show resolved Hide resolved

## Legacy versions

Older versions of the Tatum SDK has been moved to long living branches [`Tatum SDK V1`](https://github.com/tatumio/tatum-js/tree/v1) and [`Tatum SDK V2`](https://github.com/tatumio/tatum-js/tree/v2).
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tatumio/tatum",
"version": "3.1.7",
"version": "3.1.8",
"description": "Tatum JS SDK",
"author": "Tatum",
"repository": "https://github.com/tatumio/tatum-js",
Expand Down
3 changes: 3 additions & 0 deletions src/service/extensions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './tatumsdk.container'
export * from './tatumsdk.extensions.dto'

22 changes: 22 additions & 0 deletions src/service/extensions/tatumsdk.container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ContainerInstance } from "typedi";
import { ServiceConstructor } from "./tatumsdk.extensions.dto";
import { TatumConfig } from "../tatum";
import { CONFIG } from "../../util";

export interface ITatumSdkContainer {
get<T>(type: ServiceConstructor<T>): T;
}

export class TatumSdkContainer implements ITatumSdkContainer {
constructor(private readonly containerInstance: ContainerInstance) {

}

get<T>(type: ServiceConstructor<T>): T {
return this.containerInstance.get(type);
}

getConfig(): TatumConfig {
return this.containerInstance.get(CONFIG);
}
}
21 changes: 21 additions & 0 deletions src/service/extensions/tatumsdk.extensions.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { TatumSdkContainer } from "./tatumsdk.container"

export abstract class TatumSdkExtension {
protected constructor(protected readonly tatumSdkContainer: TatumSdkContainer) {
}

abstract init(...args: unknown[]): Promise<void>
abstract destroy(): void
}

export type ExtensionConstructor = new (tatumSdkContainer: TatumSdkContainer, ...args: unknown[]) => TatumSdkExtension

export type ExtensionWithConfig = {
type: ExtensionConstructor
config: unknown
}

export type ExtensionConstructorOrConfig = ExtensionConstructor | ExtensionWithConfig

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ServiceConstructor<T> = new (...args: any[]) => T
1 change: 1 addition & 0 deletions src/service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './notification'
export * from './rpc'
export * from './tatum'
export * from './walletProvider'
export * from './extensions'
6 changes: 6 additions & 0 deletions src/service/tatum/tatum.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Network } from '../../dto/Network'
import { ExtensionConstructorOrConfig } from "../extensions";

export interface TatumConfig {
/**
Expand Down Expand Up @@ -61,6 +62,11 @@ export interface TatumConfig {
*/
oneTimeLoadBalancing?: boolean
}

/**
* Optional list of TatumSdkExtensions.
*/
configureExtensions?: ExtensionConstructorOrConfig[]
}

export enum ApiVersion {
Expand Down
83 changes: 52 additions & 31 deletions src/service/tatum/tatum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,35 @@ import { LoadBalancer } from '../rpc/generic/LoadBalancer'
import { Token } from '../token'
import { WalletProvider } from '../walletProvider'
import { ApiVersion, TatumConfig } from './tatum.dto'
import { TatumSdkContainer, TatumSdkExtension } from "../extensions";

export class BaseTatumSdk {
export interface ITatumSdkChain {
extension<T extends TatumSdkExtension>(type: new (tatumSdkContainer: TatumSdkContainer, ...args: unknown[]) => T): T
}

export abstract class TatumSdkChain implements ITatumSdkChain {
protected constructor(readonly id: string) { }

extension<T extends TatumSdkExtension>(type: new (tatumSdkContainer: TatumSdkContainer, ...args: unknown[]) => T): T {
return Container.of(this.id).get(type);
}

destroy(): void {
Container.of(this.id).reset( {strategy: 'resetServices' })
}
}

export class BaseTatumSdk extends TatumSdkChain {
notification: Notification
nft: Nft
token: Token
address: Address
walletProvider: WalletProvider
rates: Rates

constructor(private readonly id: string) {
this.notification = Container.of(this.id).get(Notification)
constructor(id: string) {
super(id)
this.notification = Container.of(id).get(Notification)
this.nft = Container.of(id).get(Nft)
this.token = Container.of(id).get(Token)
this.walletProvider = Container.of(id).get(WalletProvider)
Expand All @@ -39,10 +57,6 @@ export abstract class BaseUtxoClass extends BaseTatumSdk {
this.rpc = Utils.getRpc<UtxoBasedRpcSuite>(id, Container.of(id).get(CONFIG))
this.fee = Container.of(id).get(FeeUtxo)
}

destroy(): void {
this.rpc.destroy()
}
}

export abstract class BaseEvmClass extends BaseTatumSdk {
Expand All @@ -52,10 +66,6 @@ export abstract class BaseEvmClass extends BaseTatumSdk {
super(id)
this.rpc = Utils.getRpc<EvmBasedRpcSuite>(id, Container.of(id).get(CONFIG))
}

destroy(): void {
this.rpc.destroy()
}
}

export class Ethereum extends BaseEvmClass {
Expand Down Expand Up @@ -102,23 +112,15 @@ export class Xrp extends BaseTatumSdk {
super(id)
this.rpc = Utils.getRpc<XrpRpcSuite>(id, Container.of(id).get(CONFIG))
}

destroy(): void {
this.rpc.destroy()
}
}
export class Solana extends BaseTatumSdk {
rpc: SolanaRpcSuite
constructor(id: string) {
super(id)
this.rpc = Utils.getRpc<SolanaRpcSuite>(id, Container.of(id).get(CONFIG))
}

destroy(): void {
this.rpc.destroy()
}
}
export class Tron {
export class Tron extends TatumSdkChain {
notification: Notification
nft: Nft
token: Token
Expand All @@ -127,29 +129,27 @@ export class Tron {
rates: Rates
rpc: TronRpcSuite

constructor(private readonly id: string) {
this.notification = Container.of(this.id).get(Notification)
constructor(id: string) {
super(id)
this.notification = Container.of(id).get(Notification)
this.nft = Container.of(id).get(Nft)
this.token = Container.of(id).get(Token)
this.walletProvider = Container.of(id).get(WalletProvider)
this.address = Container.of(id).get(AddressTron)
this.rates = Container.of(id).get(Rates)
this.rpc = Utils.getRpc<TronRpcSuite>(id, Container.of(id).get(CONFIG))
}

destroy(): void {
this.rpc.destroy()
}
}

export class Tezos {
export class Tezos extends TatumSdkChain {
notification: Notification
address: AddressTezos
nft: NftTezos

constructor(private readonly id: string) {
this.notification = Container.of(this.id).get(Notification)
this.address = Container.of(this.id).get(AddressTezos)
constructor(id: string) {
super(id)
this.notification = Container.of(id).get(Notification)
this.address = Container.of(id).get(AddressTezos)
this.nft = Container.of(this.id).get(NftTezos)
}
}
Expand All @@ -161,7 +161,7 @@ export class TatumSDK {
* Default configuration is used if no configuration is provided.
* @param config
*/
public static async init<T>(config: TatumConfig): Promise<T> {
public static async init<T extends ITatumSdkChain>(config: TatumConfig): Promise<T> {
const defaultConfig: Partial<TatumConfig> = {
version: ApiVersion.V4,
retryCount: 1,
Expand All @@ -184,9 +184,30 @@ export class TatumSDK {
await loadBalancer.init()
}

await this.configureExtensions(config, id)

return Utils.getClient<T>(id, mergedConfig.network)
}

private static async configureExtensions(config: TatumConfig, id: string) {
for (const extensionConfig of config?.configureExtensions ?? []) {
let type: new (container: TatumSdkContainer, ...args: unknown[]) => TatumSdkExtension
const args: unknown[] = []

if ('type' in extensionConfig) {
type = extensionConfig.type
args.push(extensionConfig.config)
} else {
type = extensionConfig
}

const containerInstance = new TatumSdkContainer(Container.of(id))
const instance = new type(containerInstance, ...args)
await instance.init(...args)
Container.of(id).set(type, instance)
}
}

private static generateRandomString() {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let result = ''
Expand Down
Loading