@0xsequence/guard 2.3.35 → 3.0.0-beta.2

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/src/index.ts CHANGED
@@ -1,2 +1,6 @@
1
- export { Guard } from './guard.gen'
2
- export * from './signer'
1
+ export * from './types.js'
2
+ export { PayloadType, SignatureType, type Signature } from './client/guard.gen.js'
3
+
4
+ export * as Client from './client/guard.gen.js'
5
+ export * as Sequence from './sequence.js'
6
+ export * as Local from './local.js'
package/src/local.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { Address, Hex, Bytes, Secp256k1, Hash } from 'ox'
2
+ import * as Client from './client/guard.gen.js'
3
+ import * as Types from './types.js'
4
+
5
+ export class Guard implements Types.Guard {
6
+ public readonly address: Address.Address
7
+
8
+ constructor(private readonly privateKey: Hex.Hex) {
9
+ const publicKey = Secp256k1.getPublicKey({ privateKey: this.privateKey })
10
+ this.address = Address.fromPublicKey(publicKey)
11
+ }
12
+
13
+ async signPayload(
14
+ wallet: Address.Address,
15
+ chainId: number,
16
+ type: Client.PayloadType,
17
+ digest: Bytes.Bytes,
18
+ message: Bytes.Bytes,
19
+ signatures?: Client.Signature[],
20
+ ) {
21
+ return Secp256k1.sign({ privateKey: this.privateKey, payload: digest })
22
+ }
23
+ }
@@ -0,0 +1,56 @@
1
+ import { Address, Hex, Signature, Bytes, Hash } from 'ox'
2
+ import * as Client from './client/guard.gen.js'
3
+ import * as Types from './types.js'
4
+
5
+ export class Guard implements Types.Guard {
6
+ private readonly guard?: Client.Guard
7
+ public readonly address: Address.Address
8
+
9
+ constructor(hostname: string, address: Address.Address, fetch?: Client.Fetch) {
10
+ if (hostname && address) {
11
+ this.guard = new Client.Guard(hostname, fetch ?? window.fetch)
12
+ }
13
+ this.address = address
14
+ }
15
+
16
+ async signPayload(
17
+ wallet: Address.Address,
18
+ chainId: number,
19
+ type: Client.PayloadType,
20
+ digest: Bytes.Bytes,
21
+ message: Bytes.Bytes,
22
+ signatures?: Client.Signature[],
23
+ token?: Client.AuthToken,
24
+ ) {
25
+ if (!this.guard || !this.address) {
26
+ throw new Error('Guard not initialized')
27
+ }
28
+
29
+ try {
30
+ const res = await this.guard.signWith({
31
+ signer: this.address,
32
+ request: {
33
+ chainId: chainId,
34
+ msg: Hex.fromBytes(digest),
35
+ wallet,
36
+ payloadType: type,
37
+ payloadData: Hex.fromBytes(message),
38
+ signatures,
39
+ },
40
+ token,
41
+ })
42
+
43
+ Hex.assert(res.sig)
44
+ return Signature.fromHex(res.sig)
45
+ } catch (error) {
46
+ if (error instanceof Client.RequiresTOTPError) {
47
+ throw new Types.AuthRequiredError('TOTP')
48
+ }
49
+ if (error instanceof Client.RequiresPINError) {
50
+ throw new Types.AuthRequiredError('PIN')
51
+ }
52
+ console.error(error)
53
+ throw new Error('Error signing with guard')
54
+ }
55
+ }
56
+ }
package/src/types.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { Address, Bytes, Signature } from 'ox'
2
+ import * as Client from './client/guard.gen.js'
3
+
4
+ export interface Guard {
5
+ readonly address: Address.Address
6
+
7
+ signPayload(
8
+ wallet: Address.Address,
9
+ chainId: number,
10
+ type: Client.PayloadType,
11
+ digest: Bytes.Bytes,
12
+ message: Bytes.Bytes,
13
+ signatures?: Client.Signature[],
14
+ token?: Client.AuthToken,
15
+ ): Promise<Signature.Signature>
16
+ }
17
+
18
+ export class AuthRequiredError extends Error {
19
+ public readonly id: 'TOTP' | 'PIN'
20
+
21
+ constructor(id: 'TOTP' | 'PIN') {
22
+ super('auth required')
23
+ this.id = id
24
+ this.name = 'AuthRequiredError'
25
+ Object.setPrototypeOf(this, AuthRequiredError.prototype)
26
+ }
27
+ }
@@ -0,0 +1,189 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { Guard } from '../src/sequence'
3
+ import { PayloadType } from '../src/client/guard.gen'
4
+ import { Address, Bytes, Hex } from 'ox'
5
+
6
+ // Mock fetch globally for guard API calls
7
+ const mockFetch = vi.fn()
8
+ global.fetch = mockFetch
9
+
10
+ describe('Sequence', () => {
11
+ describe('GuardSigner', () => {
12
+ let guard: Guard
13
+ let testWallet: Address.Address
14
+ let testMessage: Bytes.Bytes
15
+ let testMessageDigest: Bytes.Bytes
16
+
17
+ beforeEach(() => {
18
+ vi.clearAllMocks()
19
+ guard = new Guard('https://guard.sequence.app', '0xaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeae', fetch)
20
+ testWallet = '0x1234567890123456789012345678901234567890' as Address.Address
21
+ testMessage = Bytes.fromString('Test message')
22
+ testMessageDigest = Bytes.fromHex('0x1234567890abcdef1234567890abcdef1234567890')
23
+ })
24
+
25
+ afterEach(() => {
26
+ vi.resetAllMocks()
27
+ })
28
+
29
+ describe('sign()', () => {
30
+ it('Should successfully sign a payload with guard service', async () => {
31
+ const mockSignature =
32
+ '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b'
33
+
34
+ mockFetch.mockResolvedValueOnce({
35
+ json: async () => ({
36
+ sig: mockSignature,
37
+ }),
38
+ text: async () =>
39
+ JSON.stringify({
40
+ sig: mockSignature,
41
+ }),
42
+ ok: true,
43
+ })
44
+
45
+ const result = await guard.signPayload(
46
+ testWallet,
47
+ 42161,
48
+ PayloadType.ConfigUpdate,
49
+ testMessageDigest,
50
+ testMessage,
51
+ )
52
+
53
+ expect(result).toBeDefined()
54
+ expect(result.r).toBeDefined()
55
+ expect(result.s).toBeDefined()
56
+ expect(result.yParity).toBeDefined()
57
+
58
+ // Verify API call was made correctly
59
+ expect(mockFetch).toHaveBeenCalledOnce()
60
+ const [url, options] = mockFetch.mock.calls[0]
61
+
62
+ expect(url).toContain('/rpc/Guard/SignWith')
63
+ expect(options.method).toBe('POST')
64
+ expect(options.headers['Content-Type']).toBe('application/json')
65
+
66
+ const requestBody = JSON.parse(options.body)
67
+ expect(requestBody.signer).toBe('0xaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeae')
68
+ expect(requestBody.request.chainId).toBe(42161)
69
+ expect(requestBody.request.msg).toBe(Hex.fromBytes(testMessageDigest).toString())
70
+ expect(requestBody.request.payloadType).toBe(PayloadType.ConfigUpdate)
71
+ expect(requestBody.request.payloadData).toBe(Hex.fromBytes(testMessage).toString())
72
+ expect(requestBody.request.wallet).toBe(testWallet)
73
+ })
74
+
75
+ it('Should handle custom chainId in sign request', async () => {
76
+ const customChainId = 1 // Ethereum mainnet
77
+
78
+ mockFetch.mockResolvedValueOnce({
79
+ json: async () => ({
80
+ sig: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b',
81
+ }),
82
+ text: async () =>
83
+ JSON.stringify({
84
+ sig: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b',
85
+ }),
86
+ ok: true,
87
+ })
88
+
89
+ await guard.signPayload(testWallet, 1, PayloadType.ConfigUpdate, testMessageDigest, testMessage)
90
+
91
+ const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body)
92
+ expect(requestBody.request.chainId).toBe(1)
93
+ })
94
+
95
+ it('Should throw error when guard service fails', async () => {
96
+ mockFetch.mockRejectedValueOnce(new Error('Network error'))
97
+
98
+ await expect(
99
+ guard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage),
100
+ ).rejects.toThrow('Error signing with guard')
101
+ })
102
+
103
+ it('Should throw error when guard service returns invalid response', async () => {
104
+ mockFetch.mockResolvedValueOnce({
105
+ json: async () => {
106
+ throw new Error('Invalid JSON')
107
+ },
108
+ text: async () => {
109
+ throw new Error('Invalid JSON')
110
+ },
111
+ ok: true,
112
+ })
113
+
114
+ await expect(
115
+ guard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage),
116
+ ).rejects.toThrow('Error signing with guard')
117
+ })
118
+
119
+ it('Should include proper headers and signer address in request', async () => {
120
+ const mockGuardAddress = '0x9876543210987654321098765432109876543210' as Address.Address
121
+ const customGuard = new Guard('https://guard.sequence.app', mockGuardAddress, fetch)
122
+
123
+ mockFetch.mockResolvedValueOnce({
124
+ json: async () => ({
125
+ sig: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b',
126
+ }),
127
+ text: async () =>
128
+ JSON.stringify({
129
+ sig: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b',
130
+ }),
131
+ ok: true,
132
+ })
133
+
134
+ await customGuard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage)
135
+
136
+ const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body)
137
+ expect(requestBody.signer).toBe(mockGuardAddress)
138
+ })
139
+
140
+ describe('Error Handling', () => {
141
+ it('Should handle malformed guard service response', async () => {
142
+ mockFetch.mockResolvedValueOnce({
143
+ json: async () => ({
144
+ // Missing 'sig' field
145
+ error: 'Invalid request',
146
+ }),
147
+ text: async () =>
148
+ JSON.stringify({
149
+ error: 'Invalid request',
150
+ }),
151
+ ok: true,
152
+ })
153
+
154
+ await expect(
155
+ guard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage),
156
+ ).rejects.toThrow('Error signing with guard')
157
+ })
158
+
159
+ it('Should handle network timeout errors', async () => {
160
+ mockFetch.mockImplementationOnce(
161
+ () => new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 100)),
162
+ )
163
+
164
+ await expect(
165
+ guard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage),
166
+ ).rejects.toThrow('Error signing with guard')
167
+ })
168
+
169
+ it('Should handle HTTP error responses', async () => {
170
+ mockFetch.mockResolvedValueOnce({
171
+ ok: false,
172
+ status: 500,
173
+ json: async () => ({
174
+ error: 'Internal server error',
175
+ }),
176
+ text: async () =>
177
+ JSON.stringify({
178
+ error: 'Internal server error',
179
+ }),
180
+ })
181
+
182
+ await expect(
183
+ guard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage),
184
+ ).rejects.toThrow('Error signing with guard')
185
+ })
186
+ })
187
+ })
188
+ })
189
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "@repo/typescript-config/base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist",
6
+ "types": ["node"]
7
+ },
8
+ "include": ["src"],
9
+ "exclude": ["node_modules", "dist"]
10
+ }
@@ -1,2 +0,0 @@
1
- export * from "./declarations/src/index.js";
2
- //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMHhzZXF1ZW5jZS1ndWFyZC5janMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4vZGVjbGFyYXRpb25zL3NyYy9pbmRleC5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBIn0=