accounts 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/core/AccessKey.d.ts +55 -0
- package/dist/core/AccessKey.d.ts.map +1 -0
- package/dist/core/AccessKey.js +69 -0
- package/dist/core/AccessKey.js.map +1 -0
- package/dist/core/Account.d.ts +91 -0
- package/dist/core/Account.d.ts.map +1 -0
- package/dist/core/Account.js +64 -0
- package/dist/core/Account.js.map +1 -0
- package/dist/core/Adapter.d.ts +187 -0
- package/dist/core/Adapter.d.ts.map +1 -0
- package/dist/core/Adapter.js +7 -0
- package/dist/core/Adapter.js.map +1 -0
- package/dist/core/Ceremony.d.ts +109 -0
- package/dist/core/Ceremony.d.ts.map +1 -0
- package/dist/core/Ceremony.js +104 -0
- package/dist/core/Ceremony.js.map +1 -0
- package/dist/core/Client.d.ts +16 -0
- package/dist/core/Client.d.ts.map +1 -0
- package/dist/core/Client.js +18 -0
- package/dist/core/Client.js.map +1 -0
- package/dist/core/Dialog.d.ts +52 -0
- package/dist/core/Dialog.d.ts.map +1 -0
- package/dist/core/Dialog.js +342 -0
- package/dist/core/Dialog.js.map +1 -0
- package/dist/core/Expiry.d.ts +15 -0
- package/dist/core/Expiry.d.ts.map +1 -0
- package/dist/core/Expiry.js +29 -0
- package/dist/core/Expiry.js.map +1 -0
- package/dist/core/Messenger.d.ts +86 -0
- package/dist/core/Messenger.d.ts.map +1 -0
- package/dist/core/Messenger.js +127 -0
- package/dist/core/Messenger.js.map +1 -0
- package/dist/core/Provider.d.ts +69 -0
- package/dist/core/Provider.d.ts.map +1 -0
- package/dist/core/Provider.js +401 -0
- package/dist/core/Provider.js.map +1 -0
- package/dist/core/Remote.d.ts +114 -0
- package/dist/core/Remote.d.ts.map +1 -0
- package/dist/core/Remote.js +116 -0
- package/dist/core/Remote.js.map +1 -0
- package/dist/core/Schema.d.ts +805 -0
- package/dist/core/Schema.d.ts.map +1 -0
- package/dist/core/Schema.js +43 -0
- package/dist/core/Schema.js.map +1 -0
- package/dist/core/Storage.d.ts +42 -0
- package/dist/core/Storage.d.ts.map +1 -0
- package/dist/core/Storage.js +173 -0
- package/dist/core/Storage.js.map +1 -0
- package/dist/core/Store.d.ts +58 -0
- package/dist/core/Store.d.ts.map +1 -0
- package/dist/core/Store.js +58 -0
- package/dist/core/Store.js.map +1 -0
- package/dist/core/adapters/dangerous_secp256k1.d.ts +30 -0
- package/dist/core/adapters/dangerous_secp256k1.d.ts.map +1 -0
- package/dist/core/adapters/dangerous_secp256k1.js +39 -0
- package/dist/core/adapters/dangerous_secp256k1.js.map +1 -0
- package/dist/core/adapters/dialog.d.ts +31 -0
- package/dist/core/adapters/dialog.d.ts.map +1 -0
- package/dist/core/adapters/dialog.js +306 -0
- package/dist/core/adapters/dialog.js.map +1 -0
- package/dist/core/adapters/local.d.ts +33 -0
- package/dist/core/adapters/local.d.ts.map +1 -0
- package/dist/core/adapters/local.js +227 -0
- package/dist/core/adapters/local.js.map +1 -0
- package/dist/core/adapters/webAuthn.d.ts +36 -0
- package/dist/core/adapters/webAuthn.d.ts.map +1 -0
- package/dist/core/adapters/webAuthn.js +93 -0
- package/dist/core/adapters/webAuthn.js.map +1 -0
- package/dist/core/internal/withDedupe.d.ts +12 -0
- package/dist/core/internal/withDedupe.d.ts.map +1 -0
- package/dist/core/internal/withDedupe.js +12 -0
- package/dist/core/internal/withDedupe.js.map +1 -0
- package/dist/core/zod/request.d.ts +31 -0
- package/dist/core/zod/request.d.ts.map +1 -0
- package/dist/core/zod/request.js +41 -0
- package/dist/core/zod/request.js.map +1 -0
- package/dist/core/zod/rpc.d.ts +603 -0
- package/dist/core/zod/rpc.d.ts.map +1 -0
- package/dist/core/zod/rpc.js +293 -0
- package/dist/core/zod/rpc.js.map +1 -0
- package/dist/core/zod/utils.d.ts +18 -0
- package/dist/core/zod/utils.d.ts.map +1 -0
- package/dist/core/zod/utils.js +21 -0
- package/dist/core/zod/utils.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/types.d.ts +284 -0
- package/dist/internal/types.d.ts.map +1 -0
- package/dist/internal/types.js +2 -0
- package/dist/internal/types.js.map +1 -0
- package/dist/server/Handler.d.ts +257 -0
- package/dist/server/Handler.d.ts.map +1 -0
- package/dist/server/Handler.js +433 -0
- package/dist/server/Handler.js.map +1 -0
- package/dist/server/Kv.d.ts +16 -0
- package/dist/server/Kv.d.ts.map +1 -0
- package/dist/server/Kv.js +30 -0
- package/dist/server/Kv.js.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/internal/requestListener.d.ts +124 -0
- package/dist/server/internal/requestListener.d.ts.map +1 -0
- package/dist/server/internal/requestListener.js +173 -0
- package/dist/server/internal/requestListener.js.map +1 -0
- package/dist/wagmi/Connector.d.ts +93 -0
- package/dist/wagmi/Connector.d.ts.map +1 -0
- package/dist/wagmi/Connector.js +238 -0
- package/dist/wagmi/Connector.js.map +1 -0
- package/dist/wagmi/index.d.ts +3 -0
- package/dist/wagmi/index.d.ts.map +1 -0
- package/dist/wagmi/index.js +3 -0
- package/dist/wagmi/index.js.map +1 -0
- package/package.json +56 -2
- package/src/core/AccessKey.test.ts +257 -0
- package/src/core/AccessKey.ts +123 -0
- package/src/core/Account.test.ts +309 -0
- package/src/core/Account.ts +152 -0
- package/src/core/Adapter.ts +238 -0
- package/src/core/Ceremony.browser.test.ts +239 -0
- package/src/core/Ceremony.test.ts +151 -0
- package/src/core/Ceremony.ts +203 -0
- package/src/core/Client.ts +36 -0
- package/src/core/Dialog.browser.test.ts +309 -0
- package/src/core/Dialog.test-d.ts +19 -0
- package/src/core/Dialog.ts +442 -0
- package/src/core/Expiry.ts +34 -0
- package/src/core/Messenger.ts +206 -0
- package/src/core/Provider.browser.test.ts +774 -0
- package/src/core/Provider.connect.browser.test.ts +415 -0
- package/src/core/Provider.test-d.ts +53 -0
- package/src/core/Provider.test.ts +1566 -0
- package/src/core/Provider.ts +559 -0
- package/src/core/Remote.ts +262 -0
- package/src/core/Schema.test-d.ts +211 -0
- package/src/core/Schema.ts +143 -0
- package/src/core/Storage.ts +213 -0
- package/src/core/Store.test.ts +287 -0
- package/src/core/Store.ts +129 -0
- package/src/core/adapters/dangerous_secp256k1.ts +53 -0
- package/src/core/adapters/dialog.ts +379 -0
- package/src/core/adapters/local.test.ts +97 -0
- package/src/core/adapters/local.ts +277 -0
- package/src/core/adapters/webAuthn.ts +129 -0
- package/src/core/internal/withDedupe.test.ts +116 -0
- package/src/core/internal/withDedupe.ts +20 -0
- package/src/core/mppx.test.ts +83 -0
- package/src/core/zod/request.test.ts +121 -0
- package/src/core/zod/request.ts +70 -0
- package/src/core/zod/rpc.ts +374 -0
- package/src/core/zod/utils.test.ts +69 -0
- package/src/core/zod/utils.ts +40 -0
- package/src/index.ts +14 -0
- package/src/internal/types.ts +378 -0
- package/src/server/Handler.test.ts +1014 -0
- package/src/server/Handler.ts +605 -0
- package/src/server/Kv.ts +46 -0
- package/src/server/index.ts +2 -0
- package/src/server/internal/requestListener.ts +273 -0
- package/src/tsconfig.json +9 -0
- package/src/wagmi/Connector.ts +287 -0
- package/src/wagmi/index.ts +2 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Provider, type WebCryptoP256 } from 'ox'
|
|
2
|
+
import { type KeyAuthorization } from 'ox/tempo'
|
|
3
|
+
import type { Hex } from 'viem'
|
|
4
|
+
import type { Address, JsonRpcAccount } from 'viem/accounts'
|
|
5
|
+
import { Account as TempoAccount } from 'viem/tempo'
|
|
6
|
+
|
|
7
|
+
import type { OneOf } from '../internal/types.js'
|
|
8
|
+
import type * as core_Store from './Store.js'
|
|
9
|
+
|
|
10
|
+
/** Account stored in the provider state. */
|
|
11
|
+
export type Store = {
|
|
12
|
+
/** Account address. */
|
|
13
|
+
address: Address
|
|
14
|
+
} & OneOf<
|
|
15
|
+
| {}
|
|
16
|
+
| Pick<TempoAccount.Account, 'keyType' | 'sign'>
|
|
17
|
+
| { keyType: 'secp256k1'; privateKey: Hex }
|
|
18
|
+
| { keyType: 'p256'; privateKey: Hex }
|
|
19
|
+
| { keyType: 'webAuthn'; credential: { id: string; publicKey: Hex; rpId: string } }
|
|
20
|
+
| {
|
|
21
|
+
keyType: 'webCrypto'
|
|
22
|
+
keyPair: Awaited<ReturnType<typeof WebCryptoP256.createKeyPair>>
|
|
23
|
+
}
|
|
24
|
+
| {
|
|
25
|
+
keyType: 'webAuthn_headless'
|
|
26
|
+
privateKey: Hex
|
|
27
|
+
rpId: string
|
|
28
|
+
origin: string
|
|
29
|
+
}
|
|
30
|
+
>
|
|
31
|
+
|
|
32
|
+
/** Access key entry stored alongside accounts. */
|
|
33
|
+
export type AccessKey = {
|
|
34
|
+
/** Access key address. */
|
|
35
|
+
address: Address
|
|
36
|
+
/** Owner of the access key. */
|
|
37
|
+
access: Address
|
|
38
|
+
/** Unix timestamp when the access key expires. */
|
|
39
|
+
expiry?: number | undefined
|
|
40
|
+
/** Signed key authorization to attach to the first transaction. Consumed on use. */
|
|
41
|
+
keyAuthorization?: KeyAuthorization.Signed | undefined
|
|
42
|
+
/** The WebCrypto key pair backing the access key. Only present for locally-generated keys. */
|
|
43
|
+
keyPair?: Awaited<ReturnType<typeof WebCryptoP256.createKeyPair>> | undefined
|
|
44
|
+
/** Key type. */
|
|
45
|
+
keyType: 'secp256k1' | 'p256' | 'webAuthn' | 'webCrypto'
|
|
46
|
+
/** TIP-20 spending limits for the access key. */
|
|
47
|
+
limits?: { token: Address; limit: bigint }[] | undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Resolves a viem Account from the store by address (or active account). */
|
|
51
|
+
export function find(options: find.Options & { signable: true }): TempoAccount.Account
|
|
52
|
+
export function find(options: find.Options): TempoAccount.Account | JsonRpcAccount
|
|
53
|
+
export function find(options: find.Options): TempoAccount.Account | JsonRpcAccount {
|
|
54
|
+
const { accessKey = true, address, signable = false, store } = options
|
|
55
|
+
const { accessKeys, accounts, activeAccount } = store.getState()
|
|
56
|
+
|
|
57
|
+
const activeAddr = accounts[activeAccount]?.address
|
|
58
|
+
const root = address
|
|
59
|
+
? accounts.find((a) => a.address === address)
|
|
60
|
+
: accounts.find((a) => a.address === activeAddr)
|
|
61
|
+
if (!root)
|
|
62
|
+
throw address
|
|
63
|
+
? new Provider.UnauthorizedError({ message: `Account "${address}" not found.` })
|
|
64
|
+
: new Provider.DisconnectedError({ message: 'No active account.' })
|
|
65
|
+
|
|
66
|
+
// When accessKey is requested, prefer a locally-signable access key for this address.
|
|
67
|
+
if (accessKey) {
|
|
68
|
+
const key = accessKeys.find(
|
|
69
|
+
(a) => a.access.toLowerCase() === root.address.toLowerCase() && a.keyPair,
|
|
70
|
+
)
|
|
71
|
+
if (key) {
|
|
72
|
+
// Remove expired access keys.
|
|
73
|
+
if (key.expiry && key.expiry < Date.now() / 1000)
|
|
74
|
+
store.setState({ accessKeys: accessKeys.filter((a) => a !== key) })
|
|
75
|
+
else return hydrateAccessKey(key) as never
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return hydrate(root, { signable }) as never
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export declare namespace find {
|
|
83
|
+
type Options = {
|
|
84
|
+
/** Whether to resolve an access key for this account. @default true */
|
|
85
|
+
accessKey?: boolean | undefined
|
|
86
|
+
/** Address to resolve. Defaults to the active account. */
|
|
87
|
+
address?: Address | undefined
|
|
88
|
+
/** Whether to hydrate signing capability. @default false */
|
|
89
|
+
signable?: boolean | undefined
|
|
90
|
+
/** Reactive state store. */
|
|
91
|
+
store: core_Store.Store
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Overloaded signature for `find` without `store` (pre-bound by the provider). */
|
|
96
|
+
export type Find = {
|
|
97
|
+
(options: Omit<find.Options, 'store'> & { signable: true }): TempoAccount.Account
|
|
98
|
+
(options?: Omit<find.Options, 'store'>): TempoAccount.Account | JsonRpcAccount
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Hydrates an access key entry to a viem Account. Only works for locally-generated keys with a `keyPair`. */
|
|
102
|
+
export function hydrateAccessKey(accessKey: AccessKey): TempoAccount.Account {
|
|
103
|
+
if (!accessKey.keyPair)
|
|
104
|
+
throw new Provider.UnauthorizedError({
|
|
105
|
+
message: 'External access key cannot be hydrated for signing.',
|
|
106
|
+
})
|
|
107
|
+
return TempoAccount.fromWebCryptoP256(accessKey.keyPair, { access: accessKey.access })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Hydrates a store account to a viem Account. */
|
|
111
|
+
export function hydrate(account: Store, options: { signable: true }): TempoAccount.Account
|
|
112
|
+
export function hydrate(
|
|
113
|
+
account: Store,
|
|
114
|
+
options?: hydrate.Options,
|
|
115
|
+
): TempoAccount.Account | JsonRpcAccount
|
|
116
|
+
export function hydrate(
|
|
117
|
+
account: Store,
|
|
118
|
+
options: hydrate.Options = {},
|
|
119
|
+
): TempoAccount.Account | JsonRpcAccount {
|
|
120
|
+
const { signable = false } = options
|
|
121
|
+
if (!signable) return { address: account.address, type: 'json-rpc' }
|
|
122
|
+
if ('sign' in account && typeof account.sign === 'function')
|
|
123
|
+
return account as TempoAccount.Account
|
|
124
|
+
if (!account.keyType)
|
|
125
|
+
throw new Provider.UnauthorizedError({ message: `Account "${account.address}" cannot sign.` })
|
|
126
|
+
switch (account.keyType) {
|
|
127
|
+
case 'secp256k1':
|
|
128
|
+
return TempoAccount.fromSecp256k1(account.privateKey)
|
|
129
|
+
case 'p256':
|
|
130
|
+
return TempoAccount.fromP256(account.privateKey)
|
|
131
|
+
case 'webCrypto':
|
|
132
|
+
return TempoAccount.fromWebCryptoP256(account.keyPair)
|
|
133
|
+
case 'webAuthn':
|
|
134
|
+
return TempoAccount.fromWebAuthnP256(account.credential, {
|
|
135
|
+
rpId: account.credential.rpId,
|
|
136
|
+
})
|
|
137
|
+
case 'webAuthn_headless':
|
|
138
|
+
return TempoAccount.fromHeadlessWebAuthn(account.privateKey, {
|
|
139
|
+
rpId: account.rpId,
|
|
140
|
+
origin: account.origin,
|
|
141
|
+
})
|
|
142
|
+
default:
|
|
143
|
+
throw new Provider.UnauthorizedError({ message: 'Unknown key type.' })
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export declare namespace hydrate {
|
|
148
|
+
type Options = {
|
|
149
|
+
/** Whether to hydrate signing capability. @default false */
|
|
150
|
+
signable?: boolean | undefined
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import type { KeyAuthorization } from 'ox/tempo'
|
|
2
|
+
import type { Client, Hex, Transport } from 'viem'
|
|
3
|
+
import type { Address } from 'viem/accounts'
|
|
4
|
+
import type { tempo } from 'viem/chains'
|
|
5
|
+
|
|
6
|
+
import type * as Account from './Account.js'
|
|
7
|
+
import type * as Schema from './Schema.js'
|
|
8
|
+
import type * as Storage from './Storage.js'
|
|
9
|
+
import type * as Store from './Store.js'
|
|
10
|
+
import type * as Rpc from './zod/rpc.js'
|
|
11
|
+
|
|
12
|
+
/** Wire-format request (method + params) for a given RPC schema item. */
|
|
13
|
+
type EncodedRequest<encoded extends { method: unknown; params: unknown }> = Pick<
|
|
14
|
+
encoded,
|
|
15
|
+
'method' | 'params'
|
|
16
|
+
>
|
|
17
|
+
|
|
18
|
+
/** Adapter interface for the provider. */
|
|
19
|
+
export type Adapter = SetupFn & Meta
|
|
20
|
+
|
|
21
|
+
/** The setup function an adapter must implement. */
|
|
22
|
+
export type SetupFn = (params: SetupFn.Parameters) => Instance
|
|
23
|
+
|
|
24
|
+
/** Static metadata attached to an adapter function. */
|
|
25
|
+
export type Meta = {
|
|
26
|
+
/** Data URI of the provider icon. @default Black 1×1 SVG. */
|
|
27
|
+
icon?: `data:image/${string}` | undefined
|
|
28
|
+
/** Display name of the provider (e.g. `"My Wallet"`). @default "Injected Wallet" */
|
|
29
|
+
name?: string | undefined
|
|
30
|
+
/** Reverse DNS identifier (e.g. `"com.example.mywallet"`). @default `com.{lowercase name}` */
|
|
31
|
+
rdns?: string | undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type Instance = {
|
|
35
|
+
/** Adapter actions dispatched by the provider's `request()` method. */
|
|
36
|
+
actions: {
|
|
37
|
+
/** Grant an access key for the active account. */
|
|
38
|
+
authorizeAccessKey?:
|
|
39
|
+
| ((
|
|
40
|
+
params: authorizeAccessKey.Parameters,
|
|
41
|
+
request: EncodedRequest<Rpc.wallet_authorizeAccessKey.Encoded>,
|
|
42
|
+
) => Promise<authorizeAccessKey.ReturnType>)
|
|
43
|
+
| undefined
|
|
44
|
+
/** Create a new account (e.g. WebAuthn registration). */
|
|
45
|
+
createAccount: (
|
|
46
|
+
params: createAccount.Parameters,
|
|
47
|
+
request: EncodedRequest<Rpc.wallet_connect.Encoded>,
|
|
48
|
+
) => Promise<createAccount.ReturnType>
|
|
49
|
+
/** Disconnect hook for adapter-specific cleanup. */
|
|
50
|
+
disconnect?: (() => Promise<void>) | undefined
|
|
51
|
+
/** Discover existing accounts (e.g. WebAuthn assertion). */
|
|
52
|
+
loadAccounts: (
|
|
53
|
+
params: loadAccounts.Parameters | undefined,
|
|
54
|
+
request: EncodedRequest<Rpc.wallet_connect.Encoded>,
|
|
55
|
+
) => Promise<loadAccounts.ReturnType>
|
|
56
|
+
/** Revoke an access key. */
|
|
57
|
+
revokeAccessKey?:
|
|
58
|
+
| ((
|
|
59
|
+
params: revokeAccessKey.Parameters,
|
|
60
|
+
request: EncodedRequest<Rpc.wallet_revokeAccessKey.Encoded>,
|
|
61
|
+
) => Promise<void>)
|
|
62
|
+
| undefined
|
|
63
|
+
/** Send a transaction. */
|
|
64
|
+
sendTransaction: (
|
|
65
|
+
params: sendTransaction.Parameters,
|
|
66
|
+
request: EncodedRequest<Rpc.eth_sendTransaction.Encoded>,
|
|
67
|
+
) => Promise<sendTransaction.ReturnType>
|
|
68
|
+
/** Send a transaction and wait for the receipt. */
|
|
69
|
+
sendTransactionSync: (
|
|
70
|
+
params: sendTransactionSync.Parameters,
|
|
71
|
+
request: EncodedRequest<Rpc.eth_sendTransactionSync.Encoded>,
|
|
72
|
+
) => Promise<sendTransactionSync.ReturnType>
|
|
73
|
+
/** Sign a personal message (EIP-191). */
|
|
74
|
+
signPersonalMessage: (
|
|
75
|
+
params: signPersonalMessage.Parameters,
|
|
76
|
+
request: EncodedRequest<Rpc.personal_sign.Encoded>,
|
|
77
|
+
) => Promise<Hex>
|
|
78
|
+
/** Sign a transaction without broadcasting it. */
|
|
79
|
+
signTransaction: (
|
|
80
|
+
params: signTransaction.Parameters,
|
|
81
|
+
request: EncodedRequest<Rpc.eth_signTransaction.Encoded>,
|
|
82
|
+
) => Promise<signTransaction.ReturnType>
|
|
83
|
+
/** Sign EIP-712 typed data. */
|
|
84
|
+
signTypedData: (
|
|
85
|
+
params: signTypedData.Parameters,
|
|
86
|
+
request: EncodedRequest<Rpc.eth_signTypedData_v4.Encoded>,
|
|
87
|
+
) => Promise<Hex>
|
|
88
|
+
/** Switch chain hook for adapter-specific handling. */
|
|
89
|
+
switchChain?: ((params: switchChain.Parameters) => Promise<void>) | undefined
|
|
90
|
+
}
|
|
91
|
+
/** Cleanup function called when the provider is destroyed. */
|
|
92
|
+
cleanup?: (() => void) | undefined
|
|
93
|
+
/** When `true`, the provider merges new accounts onto existing ones instead of replacing. */
|
|
94
|
+
persistAccounts?: boolean | undefined
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export declare namespace SetupFn {
|
|
98
|
+
/** Parameters passed to an adapter's setup function. */
|
|
99
|
+
export type Parameters = {
|
|
100
|
+
/** Returns the rehydrated local account for the given address, or the active account if omitted. */
|
|
101
|
+
getAccount: Account.Find
|
|
102
|
+
/** Get the viem client for a given chain ID. Defaults to the active chain. */
|
|
103
|
+
getClient: (options?: getClient.Options | undefined) => Client<Transport, typeof tempo>
|
|
104
|
+
/** Storage adapter used by the provider. */
|
|
105
|
+
storage: Storage.Storage
|
|
106
|
+
/** Reactive state store. */
|
|
107
|
+
store: Store.Store
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Value returned from an adapter's setup function. */
|
|
111
|
+
export type ReturnType = Instance
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Creates an adapter from metadata and a setup function. */
|
|
115
|
+
export function define(meta: Meta, fn: SetupFn): Adapter {
|
|
116
|
+
const { name, ...rest } = meta
|
|
117
|
+
Object.defineProperty(fn, 'name', { value: name, configurable: true })
|
|
118
|
+
return Object.assign(fn, rest) as Adapter
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Spreads decoded params. */
|
|
122
|
+
export type ActionRequest<item extends Schema.Item> =
|
|
123
|
+
Schema.Decoded<item>['params'] extends readonly [infer first] ? first : never
|
|
124
|
+
|
|
125
|
+
export declare namespace getClient {
|
|
126
|
+
type Options = {
|
|
127
|
+
/** Chain ID. Defaults to the active chain. */
|
|
128
|
+
chainId?: number | undefined
|
|
129
|
+
/** Fee payer service URL. */
|
|
130
|
+
feePayer?: string | undefined
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export declare namespace createAccount {
|
|
135
|
+
type Parameters = {
|
|
136
|
+
/** Grant an access key during the ceremony. */
|
|
137
|
+
authorizeAccessKey?: authorizeAccessKey.Parameters | undefined
|
|
138
|
+
/** Digest to sign. */
|
|
139
|
+
digest?: Hex | undefined
|
|
140
|
+
/** Display name for the new account (e.g. credential name for WebAuthn). */
|
|
141
|
+
name: string
|
|
142
|
+
/** Opaque user identifier (e.g. for WebAuthn `user.id`). */
|
|
143
|
+
userId?: string | undefined
|
|
144
|
+
}
|
|
145
|
+
type ReturnType = {
|
|
146
|
+
accounts: readonly Store.Account[]
|
|
147
|
+
/** Signed key authorization, if an access key was granted. */
|
|
148
|
+
keyAuthorization?: authorizeAccessKey.ReturnType | undefined
|
|
149
|
+
/** Signature over the digest, if one was provided. */
|
|
150
|
+
signature?: Hex | undefined
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export declare namespace loadAccounts {
|
|
155
|
+
type Parameters = {
|
|
156
|
+
/** Grant an access key during the ceremony. */
|
|
157
|
+
authorizeAccessKey?: authorizeAccessKey.Parameters | undefined
|
|
158
|
+
/** Credential ID to restrict authentication to a specific credential. */
|
|
159
|
+
credentialId?: string | undefined
|
|
160
|
+
/** Digest to sign. */
|
|
161
|
+
digest?: Hex | undefined
|
|
162
|
+
/** When `true`, prompts the user to pick from all available credentials instead of using the last-used one. */
|
|
163
|
+
selectAccount?: boolean | undefined
|
|
164
|
+
}
|
|
165
|
+
type ReturnType = {
|
|
166
|
+
/** Loaded accounts. */
|
|
167
|
+
accounts: readonly Store.Account[]
|
|
168
|
+
/** Signed key authorization, if an access key was granted. */
|
|
169
|
+
keyAuthorization?: authorizeAccessKey.ReturnType | undefined
|
|
170
|
+
/** Signature over the digest, if one was provided. */
|
|
171
|
+
signature?: Hex | undefined
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export declare namespace signPersonalMessage {
|
|
176
|
+
type Parameters = {
|
|
177
|
+
/** Address of the account to sign with. */
|
|
178
|
+
address: Address
|
|
179
|
+
/** Hex-encoded message data. */
|
|
180
|
+
data: Hex
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export declare namespace signTypedData {
|
|
185
|
+
type Parameters = {
|
|
186
|
+
/** Address of the account to sign with. */
|
|
187
|
+
address: Address
|
|
188
|
+
/** JSON-encoded EIP-712 typed data. */
|
|
189
|
+
data: string
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export declare namespace authorizeAccessKey {
|
|
194
|
+
type Parameters = {
|
|
195
|
+
/** Access key address. Alternative to `publicKey` when the caller already knows the derived address. */
|
|
196
|
+
address?: Address | undefined
|
|
197
|
+
/** Unix timestamp (seconds) when the key expires. */
|
|
198
|
+
expiry: number
|
|
199
|
+
/** Key type of the external public key. Required when `publicKey` or `address` is provided. */
|
|
200
|
+
keyType?: 'secp256k1' | 'p256' | 'webAuthn' | undefined
|
|
201
|
+
/** TIP-20 spending limits for this key. */
|
|
202
|
+
limits?: readonly { token: Address; limit: bigint }[] | undefined
|
|
203
|
+
/** External public key to authorize. When provided, no key pair is generated — the caller holds the signing material. */
|
|
204
|
+
publicKey?: Hex | undefined
|
|
205
|
+
/** Pre-computed signature over the key authorization digest (skips a second signing ceremony). */
|
|
206
|
+
signature?: Hex | undefined
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
type ReturnType = KeyAuthorization.Rpc
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export declare namespace revokeAccessKey {
|
|
213
|
+
type Parameters = {
|
|
214
|
+
/** Root account address. */
|
|
215
|
+
address: Address
|
|
216
|
+
/** Address of the access key to revoke. */
|
|
217
|
+
accessKeyAddress: Address
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export declare namespace sendTransaction {
|
|
222
|
+
type Parameters = ActionRequest<typeof Rpc.eth_sendTransaction.schema>
|
|
223
|
+
type ReturnType = Rpc.eth_sendTransaction.Encoded['returns']
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export declare namespace sendTransactionSync {
|
|
227
|
+
type Parameters = ActionRequest<typeof Rpc.eth_sendTransactionSync.schema>
|
|
228
|
+
type ReturnType = Rpc.eth_sendTransactionSync.Encoded['returns']
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export declare namespace signTransaction {
|
|
232
|
+
type Parameters = ActionRequest<typeof Rpc.eth_signTransaction.schema>
|
|
233
|
+
type ReturnType = Hex
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export declare namespace switchChain {
|
|
237
|
+
type Parameters = { chainId: number }
|
|
238
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vp/test'
|
|
2
|
+
import { Registration, Authentication } from 'webauthx/client'
|
|
3
|
+
|
|
4
|
+
import { hooksUrl, url as webauthnUrl } from '../../test/webauthn.constants.js'
|
|
5
|
+
import { webAuthn } from './adapters/webAuthn.js'
|
|
6
|
+
import * as Ceremony from './Ceremony.js'
|
|
7
|
+
import * as Provider from './Provider.js'
|
|
8
|
+
import * as Storage from './Storage.js'
|
|
9
|
+
|
|
10
|
+
describe('local', () => {
|
|
11
|
+
test('default: creates a passkey and verifies registration', async () => {
|
|
12
|
+
const ceremony = Ceremony.local()
|
|
13
|
+
const { options } = await ceremony.getRegistrationOptions({ name: 'Test' })
|
|
14
|
+
const credential = await Registration.create({ options })
|
|
15
|
+
const result = await ceremony.verifyRegistration(credential)
|
|
16
|
+
|
|
17
|
+
expect(result.publicKey).toMatch(/^0x[0-9a-f]+$/)
|
|
18
|
+
expect(result.credentialId).toBeTypeOf('string')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('behavior: authenticates with an existing passkey', async () => {
|
|
22
|
+
const ceremony = Ceremony.local()
|
|
23
|
+
const { options: regOptions } = await ceremony.getRegistrationOptions({ name: 'Test' })
|
|
24
|
+
const credential = await Registration.create({ options: regOptions })
|
|
25
|
+
const { publicKey } = await ceremony.verifyRegistration(credential)
|
|
26
|
+
|
|
27
|
+
const { options: authOptions } = await ceremony.getAuthenticationOptions({
|
|
28
|
+
credentialId: credential.id,
|
|
29
|
+
})
|
|
30
|
+
const response = await Authentication.sign({ options: authOptions })
|
|
31
|
+
const result = await ceremony.verifyAuthentication(response)
|
|
32
|
+
|
|
33
|
+
expect(result.publicKey).toMatchInlineSnapshot(`"${publicKey}"`)
|
|
34
|
+
expect(result.credentialId).toBeTypeOf('string')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('behavior: register → authenticate → publicKeys match', async () => {
|
|
38
|
+
const ceremony = Ceremony.local()
|
|
39
|
+
const { options: regOptions } = await ceremony.getRegistrationOptions({ name: 'Round-trip' })
|
|
40
|
+
const credential = await Registration.create({ options: regOptions })
|
|
41
|
+
const reg = await ceremony.verifyRegistration(credential)
|
|
42
|
+
|
|
43
|
+
const { options: authOptions } = await ceremony.getAuthenticationOptions({
|
|
44
|
+
credentialId: reg.credentialId,
|
|
45
|
+
})
|
|
46
|
+
const response = await Authentication.sign({ options: authOptions })
|
|
47
|
+
const auth = await ceremony.verifyAuthentication(response)
|
|
48
|
+
|
|
49
|
+
expect(auth.publicKey).toBe(reg.publicKey)
|
|
50
|
+
expect(auth.credentialId).toBe(reg.credentialId)
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('server', () => {
|
|
55
|
+
const ceremony = Ceremony.server({ url: webauthnUrl })
|
|
56
|
+
|
|
57
|
+
test('default: register → verify returns valid publicKey hex', async () => {
|
|
58
|
+
const { options } = await ceremony.getRegistrationOptions({ name: 'Server Test' })
|
|
59
|
+
const credential = await Registration.create({ options })
|
|
60
|
+
const result = await ceremony.verifyRegistration(credential)
|
|
61
|
+
|
|
62
|
+
expect(result.credentialId).toBeTypeOf('string')
|
|
63
|
+
expect(result.publicKey).toMatch(/^0x[0-9a-f]+$/)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('behavior: authentication publicKeys match registration', async () => {
|
|
67
|
+
const { options: regOptions } = await ceremony.getRegistrationOptions({ name: 'Server Auth' })
|
|
68
|
+
const credential = await Registration.create({ options: regOptions })
|
|
69
|
+
const reg = await ceremony.verifyRegistration(credential)
|
|
70
|
+
|
|
71
|
+
const { options: authOptions } = await ceremony.getAuthenticationOptions({
|
|
72
|
+
credentialId: reg.credentialId,
|
|
73
|
+
})
|
|
74
|
+
const response = await Authentication.sign({ options: authOptions })
|
|
75
|
+
const auth = await ceremony.verifyAuthentication(response)
|
|
76
|
+
|
|
77
|
+
expect(auth.publicKey).toBe(reg.publicKey)
|
|
78
|
+
expect(auth.credentialId).toBe(reg.credentialId)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('behavior: challenge consumed after use (replay → 400)', async () => {
|
|
82
|
+
const { options } = await ceremony.getRegistrationOptions({ name: 'Replay Test' })
|
|
83
|
+
const credential = await Registration.create({ options })
|
|
84
|
+
|
|
85
|
+
// First verify succeeds
|
|
86
|
+
const result = await ceremony.verifyRegistration(credential)
|
|
87
|
+
expect(result.credentialId).toBeTypeOf('string')
|
|
88
|
+
|
|
89
|
+
// Replay same credential → 400 (challenge consumed)
|
|
90
|
+
const response = await fetch(`${webauthnUrl}/register`, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: { 'Content-Type': 'application/json' },
|
|
93
|
+
body: JSON.stringify(credential),
|
|
94
|
+
})
|
|
95
|
+
expect(response.status).toBe(400)
|
|
96
|
+
const body = await response.json()
|
|
97
|
+
expect(body.error).toMatchInlineSnapshot(`"Missing or expired challenge"`)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('behavior: multiple credentials return correct publicKeys', async () => {
|
|
101
|
+
const { options: regA } = await ceremony.getRegistrationOptions({ name: 'User A' })
|
|
102
|
+
const credA = await Registration.create({ options: regA })
|
|
103
|
+
const a = await ceremony.verifyRegistration(credA)
|
|
104
|
+
|
|
105
|
+
const { options: regB } = await ceremony.getRegistrationOptions({ name: 'User B' })
|
|
106
|
+
const credB = await Registration.create({ options: regB })
|
|
107
|
+
const b = await ceremony.verifyRegistration(credB)
|
|
108
|
+
|
|
109
|
+
expect(a.publicKey).not.toBe(b.publicKey)
|
|
110
|
+
|
|
111
|
+
// Login with credential A
|
|
112
|
+
const { options: authA } = await ceremony.getAuthenticationOptions({
|
|
113
|
+
credentialId: a.credentialId,
|
|
114
|
+
})
|
|
115
|
+
const resA = await Authentication.sign({ options: authA })
|
|
116
|
+
const authResultA = await ceremony.verifyAuthentication(resA)
|
|
117
|
+
expect(authResultA.publicKey).toBe(a.publicKey)
|
|
118
|
+
|
|
119
|
+
// Login with credential B
|
|
120
|
+
const { options: authB } = await ceremony.getAuthenticationOptions({
|
|
121
|
+
credentialId: b.credentialId,
|
|
122
|
+
})
|
|
123
|
+
const resB = await Authentication.sign({ options: authB })
|
|
124
|
+
const authResultB = await ceremony.verifyAuthentication(resB)
|
|
125
|
+
expect(authResultB.publicKey).toBe(b.publicKey)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
describe('server (provider round-trip)', () => {
|
|
130
|
+
const ceremony = Ceremony.server({ url: webauthnUrl })
|
|
131
|
+
|
|
132
|
+
function createProvider() {
|
|
133
|
+
return Provider.create({
|
|
134
|
+
storage: Storage.idb({ key: crypto.randomUUID() }),
|
|
135
|
+
adapter: webAuthn({ ceremony }),
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
test('behavior: wallet_connect register → eth_accounts returns address', async () => {
|
|
140
|
+
const provider = createProvider()
|
|
141
|
+
|
|
142
|
+
const result = await provider.request({
|
|
143
|
+
method: 'wallet_connect',
|
|
144
|
+
params: [{ capabilities: { method: 'register' } }],
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
expect(result.accounts).toHaveLength(1)
|
|
148
|
+
expect(result.accounts[0]!.address).toMatch(/^0x[0-9a-fA-F]{40}$/)
|
|
149
|
+
|
|
150
|
+
const accounts = await provider.request({ method: 'eth_accounts' })
|
|
151
|
+
expect(accounts).toHaveLength(1)
|
|
152
|
+
expect(accounts[0]).toBe(result.accounts[0]!.address)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('behavior: register two accounts → eth_accounts returns both', async () => {
|
|
156
|
+
const provider = createProvider()
|
|
157
|
+
|
|
158
|
+
// Register first
|
|
159
|
+
const first = await provider.request({
|
|
160
|
+
method: 'wallet_connect',
|
|
161
|
+
params: [{ capabilities: { method: 'register' } }],
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// Register second
|
|
165
|
+
const second = await provider.request({
|
|
166
|
+
method: 'wallet_connect',
|
|
167
|
+
params: [{ capabilities: { method: 'register' } }],
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
expect(second.accounts).toHaveLength(2)
|
|
171
|
+
expect(second.accounts[0]!.address).not.toBe(first.accounts[0]!.address)
|
|
172
|
+
|
|
173
|
+
const accounts = await provider.request({ method: 'eth_accounts' })
|
|
174
|
+
expect(accounts).toHaveLength(2)
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe('server (hooks)', () => {
|
|
179
|
+
test('behavior: onRegister merges extra JSON and headers', async () => {
|
|
180
|
+
const regOptionsRes = await fetch(`${hooksUrl}/register/options`, {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
headers: { 'Content-Type': 'application/json' },
|
|
183
|
+
body: JSON.stringify({ name: 'Hook Test' }),
|
|
184
|
+
})
|
|
185
|
+
const { options } = await regOptionsRes.json()
|
|
186
|
+
const credential = await Registration.create({ options })
|
|
187
|
+
|
|
188
|
+
const res = await fetch(`${hooksUrl}/register`, {
|
|
189
|
+
method: 'POST',
|
|
190
|
+
headers: { 'Content-Type': 'application/json' },
|
|
191
|
+
body: JSON.stringify(credential),
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
const body = await res.json()
|
|
195
|
+
expect(body.credentialId).toBeTypeOf('string')
|
|
196
|
+
expect(body.publicKey).toMatch(/^0x[0-9a-f]+$/)
|
|
197
|
+
expect(body.sessionToken).toBe(`reg_${body.credentialId}`)
|
|
198
|
+
expect(res.headers.get('x-custom')).toBe('register-hook')
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
test('behavior: onAuthenticate merges extra JSON and headers', async () => {
|
|
202
|
+
// Register first
|
|
203
|
+
const regOptionsRes = await fetch(`${hooksUrl}/register/options`, {
|
|
204
|
+
method: 'POST',
|
|
205
|
+
headers: { 'Content-Type': 'application/json' },
|
|
206
|
+
body: JSON.stringify({ name: 'Hook Auth Test' }),
|
|
207
|
+
})
|
|
208
|
+
const { options: regOptions } = await regOptionsRes.json()
|
|
209
|
+
const credential = await Registration.create({ options: regOptions })
|
|
210
|
+
|
|
211
|
+
const regRes = await fetch(`${hooksUrl}/register`, {
|
|
212
|
+
method: 'POST',
|
|
213
|
+
headers: { 'Content-Type': 'application/json' },
|
|
214
|
+
body: JSON.stringify(credential),
|
|
215
|
+
})
|
|
216
|
+
const { credentialId } = await regRes.json()
|
|
217
|
+
|
|
218
|
+
// Authenticate
|
|
219
|
+
const authOptionsRes = await fetch(`${hooksUrl}/login/options`, {
|
|
220
|
+
method: 'POST',
|
|
221
|
+
headers: { 'Content-Type': 'application/json' },
|
|
222
|
+
body: JSON.stringify({ credentialId }),
|
|
223
|
+
})
|
|
224
|
+
const { options: authOptions } = await authOptionsRes.json()
|
|
225
|
+
const response = await Authentication.sign({ options: authOptions })
|
|
226
|
+
|
|
227
|
+
const res = await fetch(`${hooksUrl}/login`, {
|
|
228
|
+
method: 'POST',
|
|
229
|
+
headers: { 'Content-Type': 'application/json' },
|
|
230
|
+
body: JSON.stringify(response),
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
const body = await res.json()
|
|
234
|
+
expect(body.credentialId).toBe(credentialId)
|
|
235
|
+
expect(body.publicKey).toMatch(/^0x[0-9a-f]+$/)
|
|
236
|
+
expect(body.sessionToken).toBe(`auth_${credentialId}`)
|
|
237
|
+
expect(res.headers.get('x-custom')).toBe('authenticate-hook')
|
|
238
|
+
})
|
|
239
|
+
})
|