@openzeppelin/guardian-evm-client 0.14.5

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.
@@ -0,0 +1,111 @@
1
+ import { normalizeBytes32, normalizeEvmAddress, normalizeSignature, normalizeUint256String, } from './encoding.js';
2
+ export function evmAccountId(chainId, smartAccountAddress) {
3
+ assertSafeChainId(chainId);
4
+ return `evm:${chainId}:${normalizeEvmAddress(smartAccountAddress)}`;
5
+ }
6
+ export function toServerRegisterAccountRequest(request) {
7
+ assertSafeChainId(request.chainId);
8
+ return {
9
+ chain_id: request.chainId,
10
+ account_address: normalizeEvmAddress(request.smartAccountAddress),
11
+ multisig_validator_address: normalizeEvmAddress(request.multisigValidatorAddress),
12
+ };
13
+ }
14
+ export function fromServerAccountRegistration(server) {
15
+ return {
16
+ accountId: stringField(server.account_id, 'account_id'),
17
+ chainId: numberField(server.chain_id, 'chain_id'),
18
+ accountAddress: normalizeEvmAddress(server.account_address),
19
+ multisigValidatorAddress: normalizeEvmAddress(server.multisig_validator_address),
20
+ signers: arrayField(server.signers, 'signers').map(normalizeEvmAddress),
21
+ threshold: numberField(server.threshold, 'threshold'),
22
+ };
23
+ }
24
+ export function toServerCreateProposalRequest(request) {
25
+ assertSafeChainId(request.chainId);
26
+ assertPositiveSeconds(request.ttlSeconds, 'ttlSeconds');
27
+ const accountId = request.accountId ?? evmAccountId(request.chainId, request.smartAccountAddress);
28
+ return {
29
+ account_id: accountId,
30
+ user_op_hash: normalizeBytes32(request.userOpHash, 'userOpHash'),
31
+ payload: request.payload,
32
+ nonce: normalizeUint256String(request.nonce),
33
+ ttl_seconds: request.ttlSeconds,
34
+ signature: normalizeSignature(request.signature),
35
+ };
36
+ }
37
+ export function toServerApproveRequest(accountId, request) {
38
+ return {
39
+ account_id: accountId,
40
+ signature: normalizeSignature(request.signature),
41
+ };
42
+ }
43
+ export function toServerCancelRequest(accountId) {
44
+ return { account_id: accountId };
45
+ }
46
+ export function fromServerProposal(server) {
47
+ return {
48
+ proposalId: normalizeBytes32(server.proposal_id, 'proposal_id'),
49
+ accountId: stringField(server.account_id, 'account_id'),
50
+ chainId: numberField(server.chain_id, 'chain_id'),
51
+ smartAccountAddress: normalizeEvmAddress(server.smart_account_address),
52
+ validatorAddress: normalizeEvmAddress(server.validator_address),
53
+ userOpHash: normalizeBytes32(server.user_op_hash, 'user_op_hash'),
54
+ payload: stringField(server.payload, 'payload'),
55
+ nonce: stringField(server.nonce, 'nonce'),
56
+ nonceKey: stringField(server.nonce_key, 'nonce_key'),
57
+ proposer: normalizeEvmAddress(server.proposer),
58
+ signerSnapshot: arrayField(server.signer_snapshot, 'signer_snapshot').map(normalizeEvmAddress),
59
+ threshold: numberField(server.threshold, 'threshold'),
60
+ signatures: arrayField(server.signatures, 'signatures').map(fromServerSignature),
61
+ createdAt: numberField(server.created_at, 'created_at'),
62
+ expiresAt: numberField(server.expires_at, 'expires_at'),
63
+ };
64
+ }
65
+ export function fromServerExecutable(server) {
66
+ return {
67
+ hash: normalizeBytes32(server.hash, 'hash'),
68
+ payload: stringField(server.payload, 'payload'),
69
+ signatures: arrayField(server.signatures, 'signatures').map(normalizeSignature),
70
+ signers: arrayField(server.signers, 'signers').map(normalizeEvmAddress),
71
+ };
72
+ }
73
+ export function accountProposalParams(accountId) {
74
+ return new URLSearchParams({ account_id: accountId });
75
+ }
76
+ function fromServerSignature(server) {
77
+ return {
78
+ signer: normalizeEvmAddress(server.signer),
79
+ signature: normalizeSignature(server.signature),
80
+ signedAt: numberField(server.signed_at, 'signed_at'),
81
+ };
82
+ }
83
+ function assertSafeChainId(chainId) {
84
+ if (!Number.isSafeInteger(chainId) || chainId <= 0) {
85
+ throw new Error('chainId must be a positive safe integer');
86
+ }
87
+ }
88
+ function assertPositiveSeconds(value, field) {
89
+ if (!Number.isSafeInteger(value) || value <= 0) {
90
+ throw new Error(`${field} must be a positive safe integer`);
91
+ }
92
+ }
93
+ function stringField(value, field) {
94
+ if (typeof value !== 'string') {
95
+ throw new Error(`${field} must be a string`);
96
+ }
97
+ return value;
98
+ }
99
+ function numberField(value, field) {
100
+ if (typeof value !== 'number' || !Number.isSafeInteger(value)) {
101
+ throw new Error(`${field} must be a safe integer`);
102
+ }
103
+ return value;
104
+ }
105
+ function arrayField(value, field) {
106
+ if (!Array.isArray(value)) {
107
+ throw new Error(`${field} must be an array`);
108
+ }
109
+ return value;
110
+ }
111
+ //# sourceMappingURL=proposals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposals.js","sourceRoot":"","sources":["../src/proposals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { Hex } from 'viem';
2
+ export type TypedDataField = {
3
+ name: string;
4
+ type: string;
5
+ };
6
+ export type GuardianTypedData = {
7
+ domain: {
8
+ name: string;
9
+ version: string;
10
+ };
11
+ types: {
12
+ EIP712Domain: TypedDataField[];
13
+ GuardianEvmSession: TypedDataField[];
14
+ };
15
+ primaryType: 'GuardianEvmSession';
16
+ message: {
17
+ wallet: `0x${string}`;
18
+ nonce: Hex;
19
+ issued_at: number;
20
+ expires_at: number;
21
+ };
22
+ };
23
+ export interface EvmSessionChallenge {
24
+ address: `0x${string}`;
25
+ nonce: Hex;
26
+ issuedAt: number;
27
+ expiresAt: number;
28
+ typedData: GuardianTypedData;
29
+ }
30
+ export declare function buildEvmSessionTypedData(input: {
31
+ address: string;
32
+ nonce: string;
33
+ issuedAt: number;
34
+ expiresAt: number;
35
+ }): GuardianTypedData;
36
+ export declare function fromServerChallenge(server: ServerChallengeResponse): EvmSessionChallenge;
37
+ interface ServerChallengeResponse {
38
+ address: string;
39
+ nonce: string;
40
+ issued_at: number;
41
+ expires_at: number;
42
+ typed_data?: unknown;
43
+ }
44
+ export {};
45
+ //# sourceMappingURL=typed-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typed-data.d.ts","sourceRoot":"","sources":["../src/typed-data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAGhC,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,KAAK,EAAE;QACL,YAAY,EAAE,cAAc,EAAE,CAAC;QAC/B,kBAAkB,EAAE,cAAc,EAAE,CAAC;KACtC,CAAC;IACF,WAAW,EAAE,oBAAoB,CAAC;IAClC,OAAO,EAAE;QACP,MAAM,EAAE,KAAK,MAAM,EAAE,CAAC;QACtB,KAAK,EAAE,GAAG,CAAC;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;IACvB,KAAK,EAAE,GAAG,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,iBAAiB,CAAC;CAC9B;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,iBAAiB,CA0BpB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,uBAAuB,GAAG,mBAAmB,CAWxF;AAED,UAAU,uBAAuB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB"}
@@ -0,0 +1,60 @@
1
+ import { normalizeBytes32, normalizeEvmAddress } from './encoding.js';
2
+ export function buildEvmSessionTypedData(input) {
3
+ return {
4
+ domain: {
5
+ name: 'Guardian EVM Session',
6
+ version: '1',
7
+ },
8
+ types: {
9
+ EIP712Domain: [
10
+ { name: 'name', type: 'string' },
11
+ { name: 'version', type: 'string' },
12
+ ],
13
+ GuardianEvmSession: [
14
+ { name: 'wallet', type: 'address' },
15
+ { name: 'nonce', type: 'bytes32' },
16
+ { name: 'issued_at', type: 'uint64' },
17
+ { name: 'expires_at', type: 'uint64' },
18
+ ],
19
+ },
20
+ primaryType: 'GuardianEvmSession',
21
+ message: {
22
+ wallet: normalizeEvmAddress(input.address),
23
+ nonce: normalizeBytes32(input.nonce, 'nonce'),
24
+ issued_at: input.issuedAt,
25
+ expires_at: input.expiresAt,
26
+ },
27
+ };
28
+ }
29
+ export function fromServerChallenge(server) {
30
+ const issuedAt = numberField(server.issued_at, 'issued_at');
31
+ const expiresAt = numberField(server.expires_at, 'expires_at');
32
+ const typedData = parseTypedData(server.typed_data, server.address, server.nonce, issuedAt, expiresAt);
33
+ return {
34
+ address: normalizeEvmAddress(server.address),
35
+ nonce: normalizeBytes32(server.nonce, 'nonce'),
36
+ issuedAt,
37
+ expiresAt,
38
+ typedData,
39
+ };
40
+ }
41
+ function parseTypedData(value, address, nonce, issuedAt, expiresAt) {
42
+ if (!isRecord(value)) {
43
+ return buildEvmSessionTypedData({ address, nonce, issuedAt, expiresAt });
44
+ }
45
+ const primaryType = value.primaryType;
46
+ if (primaryType !== 'GuardianEvmSession') {
47
+ return buildEvmSessionTypedData({ address, nonce, issuedAt, expiresAt });
48
+ }
49
+ return buildEvmSessionTypedData({ address, nonce, issuedAt, expiresAt });
50
+ }
51
+ function numberField(value, field) {
52
+ if (typeof value !== 'number' || !Number.isSafeInteger(value)) {
53
+ throw new Error(`${field} must be a safe integer`);
54
+ }
55
+ return value;
56
+ }
57
+ function isRecord(value) {
58
+ return typeof value === 'object' && value !== null;
59
+ }
60
+ //# sourceMappingURL=typed-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typed-data.js","sourceRoot":"","sources":["../src/typed-data.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AA8BtE,MAAM,UAAU,wBAAwB,CAAC,KAKxC;IACC,OAAO;QACL,MAAM,EAAE;YACN,IAAI,EAAE,sBAAsB;YAC5B,OAAO,EAAE,GAAG;SACb;QACD,KAAK,EAAE;YACL,YAAY,EAAE;gBACZ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAChC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE;aACpC;YACD,kBAAkB,EAAE;gBAClB,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE;gBACnC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE;gBAClC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACrC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE;aACvC;SACF;QACD,WAAW,EAAE,oBAAoB;QACjC,OAAO,EAAE;YACP,MAAM,EAAE,mBAAmB,CAAC,KAAK,CAAC,OAAO,CAAC;YAC1C,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC;YAC7C,SAAS,EAAE,KAAK,CAAC,QAAQ;YACzB,UAAU,EAAE,KAAK,CAAC,SAAS;SAC5B;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAA+B;IACjE,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAC5D,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,cAAc,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;IACvG,OAAO;QACL,OAAO,EAAE,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC;QAC5C,KAAK,EAAE,gBAAgB,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC;QAC9C,QAAQ;QACR,SAAS;QACT,SAAS;KACV,CAAC;AACJ,CAAC;AAUD,SAAS,cAAc,CACrB,KAAc,EACd,OAAe,EACf,KAAa,EACb,QAAgB,EAChB,SAAiB;IAEjB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,wBAAwB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IACtC,IAAI,WAAW,KAAK,oBAAoB,EAAE,CAAC;QACzC,OAAO,wBAAwB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,wBAAwB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,WAAW,CAAC,KAAc,EAAE,KAAa;IAChD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,yBAAyB,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACrD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@openzeppelin/guardian-evm-client",
3
+ "version": "0.14.5",
4
+ "description": "TypeScript EVM client for Guardian server proposal workflows",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "dev": "tsc --watch",
22
+ "typecheck": "tsc --noEmit",
23
+ "clean": "rm -rf dist",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest"
26
+ },
27
+ "dependencies": {
28
+ "viem": "^2.47.5"
29
+ },
30
+ "devDependencies": {
31
+ "typescript": "^5.7.2",
32
+ "vitest": "^2.1.8"
33
+ },
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "license": "MIT"
38
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,47 @@
1
+ import type { GuardianTypedData } from './typed-data.js';
2
+ import { normalizeBytes32, normalizeEvmAddress, normalizeSignature } from './encoding.js';
3
+
4
+ export interface Eip1193Provider {
5
+ request(args: { method: string; params?: unknown[] }): Promise<unknown>;
6
+ }
7
+
8
+ export async function requestWalletAddress(provider: Eip1193Provider): Promise<`0x${string}`> {
9
+ const accounts = await provider.request({ method: 'eth_requestAccounts' });
10
+ if (!Array.isArray(accounts) || typeof accounts[0] !== 'string') {
11
+ throw new Error('Wallet did not return an account');
12
+ }
13
+ return normalizeEvmAddress(accounts[0]);
14
+ }
15
+
16
+ export async function signTypedData(
17
+ provider: Eip1193Provider,
18
+ signerAddress: string,
19
+ typedData: GuardianTypedData
20
+ ): Promise<`0x${string}`> {
21
+ const signer = normalizeEvmAddress(signerAddress);
22
+ const signature = await provider.request({
23
+ method: 'eth_signTypedData_v4',
24
+ params: [signer, JSON.stringify(typedData)],
25
+ });
26
+ if (typeof signature !== 'string') {
27
+ throw new Error('Wallet returned a non-string EIP-712 signature');
28
+ }
29
+ return normalizeSignature(signature);
30
+ }
31
+
32
+ export async function signProposalHash(
33
+ provider: Eip1193Provider,
34
+ signerAddress: string,
35
+ hash: string
36
+ ): Promise<`0x${string}`> {
37
+ const signer = normalizeEvmAddress(signerAddress);
38
+ const normalizedHash = normalizeBytes32(hash, 'hash');
39
+ const signature = await provider.request({
40
+ method: 'eth_sign',
41
+ params: [signer, normalizedHash],
42
+ });
43
+ if (typeof signature !== 'string') {
44
+ throw new Error('Wallet returned a non-string hash signature');
45
+ }
46
+ return normalizeSignature(signature);
47
+ }
package/src/client.ts ADDED
@@ -0,0 +1,190 @@
1
+ import { requestWalletAddress, signTypedData, type Eip1193Provider } from './auth.js';
2
+ import { normalizeEvmAddress } from './encoding.js';
3
+ import { GuardianEvmHttpError } from './errors.js';
4
+ import {
5
+ fromServerAccountRegistration,
6
+ accountProposalParams,
7
+ evmAccountId,
8
+ fromServerExecutable,
9
+ fromServerProposal,
10
+ toServerApproveRequest,
11
+ toServerCancelRequest,
12
+ toServerCreateProposalRequest,
13
+ toServerRegisterAccountRequest,
14
+ type AccountRegistration,
15
+ type ApproveRequest,
16
+ type ConfigureRequest,
17
+ type ExecutableProposal,
18
+ type Proposal,
19
+ type ProposeRequest,
20
+ type ServerExecutableProposal,
21
+ type ServerListProposalsResponse,
22
+ type ServerProposal,
23
+ type ServerRegisterAccountResponse,
24
+ } from './proposals.js';
25
+ import { fromServerChallenge, type EvmSessionChallenge } from './typed-data.js';
26
+
27
+ export interface GuardianEvmClientOptions {
28
+ guardianUrl: string;
29
+ provider?: Eip1193Provider;
30
+ signerAddress?: string;
31
+ }
32
+
33
+ export interface EvmSession {
34
+ address: `0x${string}`;
35
+ expiresAt: number;
36
+ }
37
+
38
+ interface ServerVerifyResponse {
39
+ address: string;
40
+ expires_at: number;
41
+ }
42
+
43
+ export class GuardianEvmClient {
44
+ readonly guardianUrl: string;
45
+ readonly provider?: Eip1193Provider;
46
+ readonly signerAddress?: `0x${string}`;
47
+ private cookieHeader?: string;
48
+
49
+ constructor(options: GuardianEvmClientOptions) {
50
+ this.guardianUrl = options.guardianUrl.replace(/\/$/, '');
51
+ this.provider = options.provider;
52
+ this.signerAddress = options.signerAddress
53
+ ? normalizeEvmAddress(options.signerAddress)
54
+ : undefined;
55
+ }
56
+
57
+ accountId(chainId: number, smartAccountAddress: string): string {
58
+ return evmAccountId(chainId, smartAccountAddress);
59
+ }
60
+
61
+ async challenge(address?: string): Promise<EvmSessionChallenge> {
62
+ const signer = await this.resolveSigner(address);
63
+ const params = new URLSearchParams({ address: signer });
64
+ const response = await this.fetch(`/evm/auth/challenge?${params}`, { method: 'GET' });
65
+ return fromServerChallenge(await response.json());
66
+ }
67
+
68
+ async login(address?: string): Promise<EvmSession> {
69
+ const challenge = await this.challenge(address);
70
+ const provider = this.requireProvider();
71
+ const signature = await signTypedData(provider, challenge.address, challenge.typedData);
72
+ const response = await this.fetch('/evm/auth/verify', {
73
+ method: 'POST',
74
+ body: JSON.stringify({
75
+ address: challenge.address,
76
+ nonce: challenge.nonce,
77
+ signature,
78
+ }),
79
+ });
80
+ const server = (await response.json()) as ServerVerifyResponse;
81
+ return {
82
+ address: normalizeEvmAddress(server.address),
83
+ expiresAt: server.expires_at,
84
+ };
85
+ }
86
+
87
+ async logout(): Promise<void> {
88
+ await this.fetch('/evm/auth/logout', { method: 'POST' });
89
+ }
90
+
91
+ async configure(request: ConfigureRequest): Promise<AccountRegistration> {
92
+ const response = await this.fetch('/evm/accounts', {
93
+ method: 'POST',
94
+ body: JSON.stringify(toServerRegisterAccountRequest(request)),
95
+ });
96
+ return fromServerAccountRegistration((await response.json()) as ServerRegisterAccountResponse);
97
+ }
98
+
99
+ async createProposal(request: ProposeRequest): Promise<Proposal> {
100
+ const response = await this.fetch('/evm/proposals', {
101
+ method: 'POST',
102
+ body: JSON.stringify(toServerCreateProposalRequest(request)),
103
+ });
104
+ return fromServerProposal((await response.json()) as ServerProposal);
105
+ }
106
+
107
+ async listProposals(accountId: string): Promise<Proposal[]> {
108
+ const params = accountProposalParams(accountId);
109
+ const response = await this.fetch(`/evm/proposals?${params}`, { method: 'GET' });
110
+ const server = (await response.json()) as ServerListProposalsResponse;
111
+ return server.proposals.map(fromServerProposal);
112
+ }
113
+
114
+ async getProposal(accountId: string, proposalId: string): Promise<Proposal> {
115
+ const params = accountProposalParams(accountId);
116
+ const response = await this.fetch(`/evm/proposals/${proposalId}?${params}`, { method: 'GET' });
117
+ return fromServerProposal((await response.json()) as ServerProposal);
118
+ }
119
+
120
+ async approveProposal(
121
+ accountId: string,
122
+ proposalId: string,
123
+ request: ApproveRequest
124
+ ): Promise<Proposal> {
125
+ const response = await this.fetch(`/evm/proposals/${proposalId}/approve`, {
126
+ method: 'POST',
127
+ body: JSON.stringify(toServerApproveRequest(accountId, request)),
128
+ });
129
+ return fromServerProposal((await response.json()) as ServerProposal);
130
+ }
131
+
132
+ async getExecutableProposal(
133
+ accountId: string,
134
+ proposalId: string
135
+ ): Promise<ExecutableProposal> {
136
+ const params = accountProposalParams(accountId);
137
+ const response = await this.fetch(`/evm/proposals/${proposalId}/executable?${params}`, {
138
+ method: 'GET',
139
+ });
140
+ return fromServerExecutable((await response.json()) as ServerExecutableProposal);
141
+ }
142
+
143
+ async cancelProposal(accountId: string, proposalId: string): Promise<void> {
144
+ await this.fetch(`/evm/proposals/${proposalId}/cancel`, {
145
+ method: 'POST',
146
+ body: JSON.stringify(toServerCancelRequest(accountId)),
147
+ });
148
+ }
149
+
150
+ private async resolveSigner(address?: string): Promise<`0x${string}`> {
151
+ if (address) {
152
+ return normalizeEvmAddress(address);
153
+ }
154
+ if (this.signerAddress) {
155
+ return this.signerAddress;
156
+ }
157
+ return requestWalletAddress(this.requireProvider());
158
+ }
159
+
160
+ private requireProvider(): Eip1193Provider {
161
+ if (!this.provider) {
162
+ throw new Error('GuardianEvmClient requires a provider for wallet signing');
163
+ }
164
+ return this.provider;
165
+ }
166
+
167
+ private async fetch(path: string, init: RequestInit): Promise<Response> {
168
+ const response = await fetch(`${this.guardianUrl}${path}`, {
169
+ ...init,
170
+ credentials: 'include',
171
+ headers: {
172
+ 'Content-Type': 'application/json',
173
+ ...(this.cookieHeader ? { Cookie: this.cookieHeader } : {}),
174
+ ...init.headers,
175
+ },
176
+ });
177
+
178
+ if (!response.ok) {
179
+ const body = await response.text();
180
+ throw new GuardianEvmHttpError(response.status, response.statusText, body);
181
+ }
182
+
183
+ const setCookie = response.headers.get('set-cookie');
184
+ if (setCookie) {
185
+ this.cookieHeader = setCookie.split(';')[0];
186
+ }
187
+
188
+ return response;
189
+ }
190
+ }
@@ -0,0 +1,33 @@
1
+ import { getAddress, isHex, type Hex } from 'viem';
2
+
3
+ export function normalizeEvmAddress(address: string): `0x${string}` {
4
+ return getAddress(address).toLowerCase() as `0x${string}`;
5
+ }
6
+
7
+ export function normalizeBytes32(value: string, field: string): Hex {
8
+ if (!isHex(value) || value.length !== 66) {
9
+ throw new Error(`${field} must be a 32-byte 0x-prefixed hex string`);
10
+ }
11
+ return value.toLowerCase() as Hex;
12
+ }
13
+
14
+ export function normalizeSignature(value: string): Hex {
15
+ if (!isHex(value) || value.length !== 132) {
16
+ throw new Error('signature must be a 65-byte 0x-prefixed hex string');
17
+ }
18
+ return value.toLowerCase() as Hex;
19
+ }
20
+
21
+ export function normalizeUint256String(value: string): string {
22
+ const trimmed = value.trim();
23
+ if (trimmed.startsWith('0x')) {
24
+ if (!isHex(trimmed) || trimmed.length > 66) {
25
+ throw new Error('nonce must be a uint256 decimal string or 0x-prefixed hex string');
26
+ }
27
+ return trimmed.toLowerCase();
28
+ }
29
+ if (!/^[0-9]+$/.test(trimmed)) {
30
+ throw new Error('nonce must be a uint256 decimal string or 0x-prefixed hex string');
31
+ }
32
+ return trimmed;
33
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,22 @@
1
+ export class GuardianEvmHttpError extends Error {
2
+ public readonly code?: string;
3
+
4
+ constructor(
5
+ public readonly status: number,
6
+ public readonly statusText: string,
7
+ public readonly body: string
8
+ ) {
9
+ super(`Guardian EVM HTTP error ${status}: ${statusText} - ${body}`);
10
+ this.name = 'GuardianEvmHttpError';
11
+ this.code = parseErrorCode(body);
12
+ }
13
+ }
14
+
15
+ function parseErrorCode(body: string): string | undefined {
16
+ try {
17
+ const parsed = JSON.parse(body) as { code?: unknown };
18
+ return typeof parsed.code === 'string' ? parsed.code : undefined;
19
+ } catch {
20
+ return undefined;
21
+ }
22
+ }