Skip to content

Commit 416427d

Browse files
committed
feat: Turnkey server side wallets
1 parent e876a00 commit 416427d

File tree

8 files changed

+755
-8
lines changed

8 files changed

+755
-8
lines changed

packages/sdk/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@
6666
"@dynamic-labs/ethereum": ">=4.31.4",
6767
"@dynamic-labs/wallet-connector-core": ">=4.31.4",
6868
"@dynamic-labs/waas-evm": ">=4.31.4",
69-
"@privy-io/server-auth": ">=1.28.0"
69+
"@privy-io/server-auth": ">=1.28.0",
70+
"@turnkey/core": ">=1.1.1",
71+
"@turnkey/http": ">=3.12.1",
72+
"@turnkey/sdk-server": ">=4.9.1",
73+
"@turnkey/viem": ">=0.14.1"
7074
},
7175
"devDependencies": {
7276
"@types/node": "^18",

packages/sdk/src/nodeVerbsFactory.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { VerbsConfig } from '@/types/verbs.js'
22
import { Verbs } from '@/verbs.js'
3+
import type { NodeHostedProviderType } from '@/wallet/providers/hostedProvider.types.js'
34
import { NodeHostedWalletProviderRegistry } from '@/wallet/providers/NodeHostedWalletProviderRegistry.js'
45

56
/**
@@ -11,8 +12,10 @@ import { NodeHostedWalletProviderRegistry } from '@/wallet/providers/NodeHostedW
1112
* @param config Verbs configuration
1213
* @returns Verbs instance using the NodeHostedWalletProviderRegistry
1314
*/
14-
export function createVerbs(config: VerbsConfig<'privy'>) {
15-
return new Verbs(config, {
15+
export function createVerbs<T extends NodeHostedProviderType>(
16+
config: VerbsConfig<T>,
17+
) {
18+
return new Verbs<T>(config, {
1619
hostedWalletProviderRegistry: new NodeHostedWalletProviderRegistry(),
1720
})
1821
}

packages/sdk/src/types/wallet.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,12 @@ export type PrivyHostedWalletToVerbsWalletOptions = {
4040
export type DynamicHostedWalletToVerbsWalletOptions = {
4141
wallet: DynamicWallet
4242
}
43+
44+
/**
45+
* Options for converting a Turnkey hosted wallet to a Verbs wallet
46+
* @description Parameters for converting a hosted wallet to a Verbs wallet
47+
*/
48+
export type TurnkeyHostedWalletToVerbsWalletOptions = {
49+
signWith: string
50+
ethereumAddress?: string
51+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import type { TurnkeySDKClientBase } from '@turnkey/core'
2+
import type { TurnkeyClient } from '@turnkey/http'
3+
import type { TurnkeyServerClient } from '@turnkey/sdk-server'
4+
import { createAccount } from '@turnkey/viem'
5+
import type { Address, LocalAccount, WalletClient } from 'viem'
6+
import { createWalletClient, fallback, http } from 'viem'
7+
8+
import type { SupportedChainId } from '@/constants/supportedChains.js'
9+
import type { ChainManager } from '@/services/ChainManager.js'
10+
import { Wallet } from '@/wallet/base/Wallet.js'
11+
12+
/**
13+
* Turnkey wallet implementation
14+
* @description Wallet implementation using Turnkey service
15+
*/
16+
export class TurnkeyWallet extends Wallet {
17+
public address!: Address
18+
public signer!: LocalAccount
19+
/**
20+
* Turnkey client instance (HTTP, server, or core SDK base)
21+
*/
22+
private readonly client:
23+
| TurnkeyClient
24+
| TurnkeyServerClient
25+
| TurnkeySDKClientBase
26+
/**
27+
* Turnkey organization ID that owns the signing key
28+
*/
29+
private readonly organizationId: string
30+
/**
31+
* This can be a wallet account address, private key address, or private key ID.
32+
*/
33+
private readonly signWith: string
34+
/**
35+
* Ethereum address to use for this account, in the case that a private key ID is used to sign.
36+
* If left undefined, `createAccount` will fetch it from the Turnkey API.
37+
* We recommend setting this if you're using a passkey client, so that your users are not prompted for a passkey signature just to fetch their address.
38+
* You may leave this undefined if using an API key client.
39+
*/
40+
private readonly ethereumAddress?: string
41+
42+
private constructor(params: {
43+
chainManager: ChainManager
44+
client: TurnkeyClient | TurnkeyServerClient | TurnkeySDKClientBase
45+
organizationId: string
46+
signWith: string
47+
ethereumAddress?: string
48+
}) {
49+
const { chainManager, client, organizationId, signWith, ethereumAddress } =
50+
params
51+
super(chainManager)
52+
this.client = client
53+
this.organizationId = organizationId
54+
this.signWith = signWith
55+
this.ethereumAddress = ethereumAddress
56+
}
57+
58+
static async create(params: {
59+
chainManager: ChainManager
60+
client: TurnkeyClient | TurnkeyServerClient | TurnkeySDKClientBase
61+
organizationId: string
62+
signWith: string
63+
ethereumAddress?: string
64+
}): Promise<TurnkeyWallet> {
65+
const wallet = new TurnkeyWallet(params)
66+
await wallet.initialize()
67+
return wallet
68+
}
69+
70+
async walletClient(chainId: SupportedChainId): Promise<WalletClient> {
71+
const rpcUrls = this.chainManager.getRpcUrls(chainId)
72+
return createWalletClient({
73+
account: this.signer,
74+
chain: this.chainManager.getChain(chainId),
75+
transport: rpcUrls?.length
76+
? fallback(rpcUrls.map((rpcUrl) => http(rpcUrl)))
77+
: http(),
78+
})
79+
}
80+
81+
protected async performInitialization() {
82+
this.signer = await this.createAccount()
83+
this.address = this.signer.address
84+
}
85+
86+
private async createAccount(): Promise<LocalAccount> {
87+
return createAccount({
88+
client: this.client,
89+
organizationId: this.organizationId,
90+
signWith: this.signWith,
91+
ethereumAddress: this.ethereumAddress,
92+
})
93+
}
94+
}

packages/sdk/src/wallet/providers/NodeHostedWalletProviderRegistry.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { HostedWalletProviderRegistry } from '@/wallet/providers/base/HostedWalletProviderRegistry.js'
2-
import type { PrivyOptions } from '@/wallet/providers/hostedProvider.types.js'
2+
import type {
3+
PrivyOptions,
4+
TurnkeyOptions,
5+
} from '@/wallet/providers/hostedProvider.types.js'
36
import { PrivyHostedWalletProvider } from '@/wallet/providers/PrivyHostedWalletProvider.js'
7+
import { TurnkeyHostedWalletProvider } from '@/wallet/providers/TurnkeyHostedWalletProvider.js'
48

59
/**
610
* Node environment hosted wallet registry.
@@ -18,5 +22,20 @@ export class NodeHostedWalletProviderRegistry extends HostedWalletProviderRegist
1822
return new PrivyHostedWalletProvider(options.privyClient, chainManager)
1923
},
2024
})
25+
26+
this.register<'turnkey'>({
27+
type: 'turnkey',
28+
validateOptions(options): options is TurnkeyOptions {
29+
const o = options as TurnkeyOptions
30+
return Boolean(o?.client) && typeof o?.organizationId === 'string'
31+
},
32+
create({ chainManager }, options) {
33+
return new TurnkeyHostedWalletProvider(
34+
options.client,
35+
options.organizationId,
36+
chainManager,
37+
)
38+
},
39+
})
2140
}
2241
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { TurnkeySDKClientBase } from '@turnkey/core'
2+
import type { TurnkeyClient as TurnkeyHttpClient } from '@turnkey/http'
3+
import type { TurnkeyServerClient } from '@turnkey/sdk-server'
4+
5+
import type { ChainManager } from '@/services/ChainManager.js'
6+
import type { TurnkeyHostedWalletToVerbsWalletOptions } from '@/types/wallet.js'
7+
import type { Wallet } from '@/wallet/base/Wallet.js'
8+
import { HostedWalletProvider } from '@/wallet/providers/base/HostedWalletProvider.js'
9+
import { TurnkeyWallet } from '@/wallet/TurnkeyWallet.js'
10+
11+
/**
12+
* Turnkey wallet provider implementation
13+
* @description Hosted wallet provider that wraps Turnkey's signing infrastructure
14+
* and exposes a Verbs-compatible wallet. This provider is intended for Node
15+
* environments where the Turnkey client (HTTP, server, or core SDK) and
16+
* organization context are provided at construction time.
17+
*/
18+
export class TurnkeyHostedWalletProvider extends HostedWalletProvider<'turnkey'> {
19+
/**
20+
* Create a new Turnkey wallet provider
21+
* @param client - Turnkey client instance (HTTP, server, or core SDK base)
22+
* @param organizationId - Turnkey organization ID that owns the signing key
23+
* @param chainManager - Chain manager used to resolve chains and RPC transports
24+
*/
25+
constructor(
26+
private readonly client:
27+
| TurnkeyHttpClient
28+
| TurnkeyServerClient
29+
| TurnkeySDKClientBase,
30+
private readonly organizationId: string,
31+
chainManager: ChainManager,
32+
) {
33+
super(chainManager)
34+
}
35+
36+
/**
37+
* Convert a Turnkey hosted wallet context into a Verbs wallet
38+
* @description Creates a `TurnkeyWallet` configured with the provider's Turnkey
39+
* client and organization.
40+
* @param params - Options for creating the Verbs wallet from Turnkey context
41+
* @param params.signWith - Wallet account address, private key address, or private key ID
42+
* @param params.ethereumAddress - Ethereum address to use for this account, in the case that a private key ID is used to sign.
43+
* @returns Promise resolving to a Verbs-compatible wallet instance
44+
*/
45+
async toVerbsWallet(
46+
params: TurnkeyHostedWalletToVerbsWalletOptions,
47+
): Promise<Wallet> {
48+
return TurnkeyWallet.create({
49+
client: this.client,
50+
organizationId: this.organizationId,
51+
signWith: params.signWith,
52+
ethereumAddress: params.ethereumAddress,
53+
chainManager: this.chainManager,
54+
})
55+
}
56+
}

packages/sdk/src/wallet/providers/hostedProvider.types.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,59 @@
11
import type { PrivyClient } from '@privy-io/server-auth'
2+
import type { TurnkeySDKClientBase } from '@turnkey/core'
3+
import type { TurnkeyClient as TurnkeyHttpClient } from '@turnkey/http'
4+
import type { TurnkeyServerClient } from '@turnkey/sdk-server'
25

36
import type { ChainManager } from '@/services/ChainManager.js'
47
import type {
58
DynamicHostedWalletToVerbsWalletOptions,
69
PrivyHostedWalletToVerbsWalletOptions,
10+
TurnkeyHostedWalletToVerbsWalletOptions,
711
} from '@/types/wallet.js'
812
import type { DynamicHostedWalletProvider } from '@/wallet/providers/DynamicHostedWalletProvider.js'
913
import type { PrivyHostedWalletProvider } from '@/wallet/providers/PrivyHostedWalletProvider.js'
14+
import type { TurnkeyHostedWalletProvider } from '@/wallet/providers/TurnkeyHostedWalletProvider.js'
1015

1116
export interface PrivyOptions {
1217
privyClient: PrivyClient
1318
}
1419

20+
export interface TurnkeyOptions {
21+
/**
22+
* Turnkey client instance (HTTP, server, or core SDK base)
23+
*/
24+
client: TurnkeyHttpClient | TurnkeyServerClient | TurnkeySDKClientBase
25+
/**
26+
* Turnkey organization ID that owns the signing key
27+
*/
28+
organizationId: string
29+
}
30+
1531
export type DynamicOptions = undefined
1632
export interface HostedProviderConfigMap {
1733
privy: PrivyOptions
1834
dynamic: DynamicOptions
35+
turnkey: TurnkeyOptions
1936
}
2037

2138
export interface HostedProviderInstanceMap {
2239
privy: PrivyHostedWalletProvider
2340
dynamic: DynamicHostedWalletProvider
41+
turnkey: TurnkeyHostedWalletProvider
2442
}
2543

2644
export interface HostedWalletToVerbsOptionsMap {
2745
privy: PrivyHostedWalletToVerbsWalletOptions
2846
dynamic: DynamicHostedWalletToVerbsWalletOptions
47+
turnkey: TurnkeyHostedWalletToVerbsWalletOptions
2948
}
3049

3150
export type HostedProviderType = keyof HostedProviderConfigMap
3251

52+
export type NodeHostedProviderType = Extract<
53+
HostedProviderType,
54+
'privy' | 'turnkey'
55+
>
56+
3357
export type HostedWalletToVerbsType = keyof HostedWalletToVerbsOptionsMap
3458

3559
export interface HostedProviderDeps {

0 commit comments

Comments
 (0)