emblem-vault-ai-signers 0.1.0
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/README.md +210 -0
- package/dist/ethers.d.ts +15 -0
- package/dist/ethers.js +71 -0
- package/dist/http.d.ts +3 -0
- package/dist/http.js +28 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +39 -0
- package/dist/solana.d.ts +9 -0
- package/dist/solana.js +20 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.js +50 -0
- package/dist/vault.d.ts +2 -0
- package/dist/vault.js +17 -0
- package/dist/viem.d.ts +20 -0
- package/dist/viem.js +42 -0
- package/dist/web3.d.ts +12 -0
- package/dist/web3.js +43 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Emblem Vault AI Signers
|
|
2
|
+
|
|
3
|
+
Remote signer adapters for Emblem Vault that plug into popular Ethereum libraries:
|
|
4
|
+
|
|
5
|
+
- `toViemAccount()` – creates a viem `Account` that signs via Emblem
|
|
6
|
+
- `toEthersWallet()` – creates an ethers v6 `Signer` that signs via Emblem
|
|
7
|
+
- `toWeb3Adapter()` – returns a minimal Web3-style signer adapter (EVM)
|
|
8
|
+
- `toSolanaWeb3Signer()` – returns a stub Solana signer with `publicKey`
|
|
9
|
+
- `toSolanaKitSigner()` – returns a stub Solana signer with `publicKey`
|
|
10
|
+
|
|
11
|
+
> Note: The ethers adapter targets ethers v6.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
npm install emblem-vault-ai-signers
|
|
17
|
+
# and bring your own peers
|
|
18
|
+
npm install ethers viem
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { createEmblemClient } from "emblem-vault-ai-signers";
|
|
25
|
+
import { createPublicClient, http } from "viem";
|
|
26
|
+
import { mainnet } from "viem/chains";
|
|
27
|
+
import { JsonRpcProvider } from "ethers";
|
|
28
|
+
|
|
29
|
+
const client = createEmblemClient({
|
|
30
|
+
apiKey: "your-x-api-key",
|
|
31
|
+
// baseUrl: "https://api.emblemvault.ai" // optional (tests use https://dev-api.emblemvault.ai)
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// viem
|
|
35
|
+
const account = await client.toViemAccount();
|
|
36
|
+
const viemClient = createPublicClient({ chain: mainnet, transport: http() });
|
|
37
|
+
// e.g. viemClient.signMessage({ account, message: "hello" })
|
|
38
|
+
|
|
39
|
+
// ethers v6
|
|
40
|
+
const provider = new JsonRpcProvider(process.env.RPC_URL!);
|
|
41
|
+
const wallet = await client.toEthersWallet(provider);
|
|
42
|
+
// e.g. await wallet.sendTransaction({ to: "0x...", value: 1n })
|
|
43
|
+
|
|
44
|
+
// web3.js-like adapter (minimal)
|
|
45
|
+
const web3Adapter = await client.toWeb3Adapter();
|
|
46
|
+
await web3Adapter.signMessage("hello");
|
|
47
|
+
|
|
48
|
+
// Solana stubs (address only; signing not yet implemented)
|
|
49
|
+
const solWeb3 = await client.toSolanaWeb3Signer();
|
|
50
|
+
console.log(solWeb3.publicKey);
|
|
51
|
+
const solKit = await client.toSolanaKitSigner();
|
|
52
|
+
console.log(solKit.publicKey);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Replace Private Keys (Examples)
|
|
56
|
+
|
|
57
|
+
Below are quick swaps showing how to remove local private keys and route signing through Emblem.
|
|
58
|
+
|
|
59
|
+
### viem
|
|
60
|
+
|
|
61
|
+
Old (local private key):
|
|
62
|
+
```ts
|
|
63
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
64
|
+
import { createWalletClient, http } from "viem";
|
|
65
|
+
import { sepolia } from "viem/chains";
|
|
66
|
+
|
|
67
|
+
const account = privateKeyToAccount(process.env.PK as `0x${string}`);
|
|
68
|
+
const wallet = createWalletClient({ chain: sepolia, transport: http(process.env.RPC_URL!), account });
|
|
69
|
+
await wallet.sendTransaction({ to: "0x...", value: 1n });
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
New (Emblem remote signer):
|
|
73
|
+
```ts
|
|
74
|
+
import { createEmblemClient } from "emblem-vault-ai-signers";
|
|
75
|
+
import { createWalletClient, http } from "viem";
|
|
76
|
+
import { sepolia } from "viem/chains";
|
|
77
|
+
|
|
78
|
+
const client = createEmblemClient({ apiKey: process.env.EMBLEM_API_KEY!, baseUrl: process.env.EMBLEM_BASE_URL });
|
|
79
|
+
const account = await client.toViemAccount();
|
|
80
|
+
const wallet = createWalletClient({ chain: sepolia, transport: http(process.env.RPC_URL!), account });
|
|
81
|
+
|
|
82
|
+
// Message & typed data
|
|
83
|
+
await wallet.signMessage({ account, message: "hello" });
|
|
84
|
+
await wallet.signTypedData({
|
|
85
|
+
account,
|
|
86
|
+
domain: { name: "App", version: "1", chainId: 11155111 },
|
|
87
|
+
types: { Test: [{ name: "x", type: "uint256" }] },
|
|
88
|
+
primaryType: "Test",
|
|
89
|
+
message: { x: 42 },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Transactions
|
|
93
|
+
await wallet.sendTransaction({ account, to: "0x...", value: 1n });
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### ethers v6
|
|
97
|
+
|
|
98
|
+
Old (local private key):
|
|
99
|
+
```ts
|
|
100
|
+
import { Wallet, JsonRpcProvider } from "ethers";
|
|
101
|
+
const provider = new JsonRpcProvider(process.env.RPC_URL!);
|
|
102
|
+
const wallet = new Wallet(process.env.PK!, provider);
|
|
103
|
+
await wallet.sendTransaction({ to: "0x...", value: 1n });
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
New (Emblem remote signer):
|
|
107
|
+
```ts
|
|
108
|
+
import { createEmblemClient } from "emblem-vault-ai-signers";
|
|
109
|
+
import { JsonRpcProvider } from "ethers";
|
|
110
|
+
|
|
111
|
+
const client = createEmblemClient({ apiKey: process.env.EMBLEM_API_KEY!, baseUrl: process.env.EMBLEM_BASE_URL });
|
|
112
|
+
const provider = new JsonRpcProvider(process.env.RPC_URL!);
|
|
113
|
+
const wallet = await client.toEthersWallet(provider);
|
|
114
|
+
|
|
115
|
+
await wallet.signMessage("hello");
|
|
116
|
+
await wallet.sendTransaction({ to: "0x...", value: 1n });
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### web3.js (minimal adapter)
|
|
120
|
+
|
|
121
|
+
Emblem includes a small adapter that returns signatures and raw transactions you can broadcast.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { createEmblemClient } from "emblem-vault-ai-signers";
|
|
125
|
+
import { JsonRpcProvider } from "ethers"; // for broadcasting raw tx
|
|
126
|
+
|
|
127
|
+
const client = createEmblemClient({ apiKey: process.env.EMBLEM_API_KEY!, baseUrl: process.env.EMBLEM_BASE_URL });
|
|
128
|
+
const adapter = await client.toWeb3Adapter();
|
|
129
|
+
|
|
130
|
+
// Sign message / typed data
|
|
131
|
+
const sig1 = await adapter.signMessage("hello");
|
|
132
|
+
const sig2 = await adapter.signTypedData(
|
|
133
|
+
{ name: "App", version: "1", chainId: 11155111 },
|
|
134
|
+
{ Test: [{ name: "x", type: "uint256" }] },
|
|
135
|
+
{ x: 42 }
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Sign transaction, then broadcast using ethers provider
|
|
139
|
+
const { rawTransaction } = await adapter.signTransaction({
|
|
140
|
+
to: "0x...",
|
|
141
|
+
value: 1n,
|
|
142
|
+
chainId: 11155111,
|
|
143
|
+
gas: 21000n,
|
|
144
|
+
maxFeePerGas: 1n,
|
|
145
|
+
maxPriorityFeePerGas: 1n,
|
|
146
|
+
});
|
|
147
|
+
const provider = new JsonRpcProvider(process.env.RPC_URL!);
|
|
148
|
+
await provider.broadcastTransaction(rawTransaction);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Solana (stubs)
|
|
152
|
+
|
|
153
|
+
These expose the Solana address derived from the vault. Signing is not implemented yet.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const solWeb3 = await client.toSolanaWeb3Signer();
|
|
157
|
+
console.log(solWeb3.publicKey);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## API
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
type EmblemRemoteConfig = {
|
|
164
|
+
apiKey: string;
|
|
165
|
+
baseUrl?: string; // default https://api.emblemvault.ai
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
createEmblemClient(config): EmblemVaultClient
|
|
169
|
+
EmblemVaultClient#toViemAccount(): Promise<Account>
|
|
170
|
+
EmblemVaultClient#toEthersWallet(provider?): Promise<Signer>
|
|
171
|
+
EmblemVaultClient#toWeb3Adapter(): Promise<{ address, signMessage, signTypedData, signTransaction }>
|
|
172
|
+
EmblemVaultClient#toSolanaWeb3Signer(): Promise<{ publicKey }>
|
|
173
|
+
EmblemVaultClient#toSolanaKitSigner(): Promise<{ publicKey }>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Adapters POST to the Emblem API endpoints:
|
|
177
|
+
|
|
178
|
+
- `POST /sign-eth-message` – `{ vaultId, message }`
|
|
179
|
+
- `POST /sign-typed-message` – `{ vaultId, domain, types, message }`
|
|
180
|
+
- `POST /sign-eth-tx` – `{ vaultId, transaction }` (expects ethers-serializable fields)
|
|
181
|
+
|
|
182
|
+
On first use, both adapters query `GET /vault/info` with header `x-api-key` to obtain:
|
|
183
|
+
|
|
184
|
+
- Vault ID
|
|
185
|
+
- Solana Address
|
|
186
|
+
- EVM Address
|
|
187
|
+
|
|
188
|
+
Transactions are normalized to hex/number-like fields before submission.
|
|
189
|
+
|
|
190
|
+
## Testing
|
|
191
|
+
|
|
192
|
+
- Copy `.env.example` to `.env` and set:
|
|
193
|
+
- `EMBLEM_API_KEY` – your dev API key
|
|
194
|
+
- `EMBLEM_BASE_URL` – usually `https://dev-api.emblemvault.ai`
|
|
195
|
+
- Run `npm test`
|
|
196
|
+
|
|
197
|
+
Notes:
|
|
198
|
+
- Tests mock `fetch` and do not hit the network.
|
|
199
|
+
- Node 18+ (or a fetch polyfill) is required at runtime.
|
|
200
|
+
|
|
201
|
+
### Integration tests
|
|
202
|
+
|
|
203
|
+
Integration tests hit the real API using your `.env` values and verify signatures with viem/ethers.
|
|
204
|
+
|
|
205
|
+
- Ensure `.env` contains working credentials (typically dev base URL)
|
|
206
|
+
- Run: `npm run test:integration`
|
|
207
|
+
|
|
208
|
+
These tests:
|
|
209
|
+
- Sign and verify messages and typed data
|
|
210
|
+
- Sign transactions and verify the recovered `from` address
|
package/dist/ethers.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EmblemRemoteConfig, VaultInfo } from "./types";
|
|
2
|
+
import { AbstractSigner } from "ethers";
|
|
3
|
+
import type { Provider, TransactionRequest, TransactionResponse, TypedDataDomain, TypedDataField } from "ethers";
|
|
4
|
+
export declare class EmblemEthersWallet extends AbstractSigner {
|
|
5
|
+
#private;
|
|
6
|
+
readonly address: `0x${string}`;
|
|
7
|
+
constructor(address: `0x${string}`, vaultId: string, config: EmblemRemoteConfig, provider?: Provider | null);
|
|
8
|
+
getAddress(): Promise<string>;
|
|
9
|
+
connect(provider: Provider): EmblemEthersWallet;
|
|
10
|
+
signMessage(message: string | Uint8Array): Promise<string>;
|
|
11
|
+
signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string>;
|
|
12
|
+
signTransaction(tx: TransactionRequest): Promise<string>;
|
|
13
|
+
sendTransaction(tx: TransactionRequest): Promise<TransactionResponse>;
|
|
14
|
+
}
|
|
15
|
+
export declare function toEthersWallet(config: EmblemRemoteConfig, provider?: Provider | null, infoOverride?: VaultInfo): Promise<EmblemEthersWallet>;
|
package/dist/ethers.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _EmblemEthersWallet_config, _EmblemEthersWallet_vaultId;
|
|
13
|
+
import { emblemPost } from "./http";
|
|
14
|
+
import { bytesToHex, normalizeTxForEmblem } from "./utils";
|
|
15
|
+
import { fetchVaultInfo } from "./vault";
|
|
16
|
+
import { AbstractSigner, } from "ethers";
|
|
17
|
+
export class EmblemEthersWallet extends AbstractSigner {
|
|
18
|
+
constructor(address, vaultId, config, provider) {
|
|
19
|
+
super(provider ?? null);
|
|
20
|
+
_EmblemEthersWallet_config.set(this, void 0);
|
|
21
|
+
_EmblemEthersWallet_vaultId.set(this, void 0);
|
|
22
|
+
this.address = address;
|
|
23
|
+
__classPrivateFieldSet(this, _EmblemEthersWallet_vaultId, vaultId, "f");
|
|
24
|
+
__classPrivateFieldSet(this, _EmblemEthersWallet_config, config, "f");
|
|
25
|
+
}
|
|
26
|
+
async getAddress() {
|
|
27
|
+
return this.address;
|
|
28
|
+
}
|
|
29
|
+
connect(provider) {
|
|
30
|
+
return new EmblemEthersWallet(this.address, __classPrivateFieldGet(this, _EmblemEthersWallet_vaultId, "f"), __classPrivateFieldGet(this, _EmblemEthersWallet_config, "f"), provider);
|
|
31
|
+
}
|
|
32
|
+
async signMessage(message) {
|
|
33
|
+
const vaultId = __classPrivateFieldGet(this, _EmblemEthersWallet_vaultId, "f");
|
|
34
|
+
const payload = typeof message === "string" ? message : bytesToHex(message);
|
|
35
|
+
const data = await emblemPost("/sign-eth-message", { vaultId, message: payload }, __classPrivateFieldGet(this, _EmblemEthersWallet_config, "f"));
|
|
36
|
+
return data.signature;
|
|
37
|
+
}
|
|
38
|
+
async signTypedData(domain, types, value) {
|
|
39
|
+
const vaultId = __classPrivateFieldGet(this, _EmblemEthersWallet_vaultId, "f");
|
|
40
|
+
const data = await emblemPost("/sign-typed-message", { vaultId, domain, types, message: value }, __classPrivateFieldGet(this, _EmblemEthersWallet_config, "f"));
|
|
41
|
+
return data.signature;
|
|
42
|
+
}
|
|
43
|
+
async signTransaction(tx) {
|
|
44
|
+
// Ensure `from` (if present) matches this signer
|
|
45
|
+
const from = tx.from;
|
|
46
|
+
if (from && from.toLowerCase() !== this.address.toLowerCase()) {
|
|
47
|
+
throw new Error("transaction from does not match signer address");
|
|
48
|
+
}
|
|
49
|
+
// Let provider fill fields if available
|
|
50
|
+
const toSign = this.provider ? await this.populateTransaction(tx) : { ...tx };
|
|
51
|
+
// Ethers serializers do not include `from`
|
|
52
|
+
delete toSign.from;
|
|
53
|
+
const normalized = normalizeTxForEmblem(toSign);
|
|
54
|
+
const vaultId = __classPrivateFieldGet(this, _EmblemEthersWallet_vaultId, "f");
|
|
55
|
+
const resp = await emblemPost("/sign-eth-tx", { vaultId, transaction: normalized }, __classPrivateFieldGet(this, _EmblemEthersWallet_config, "f"));
|
|
56
|
+
return resp.signedTransaction;
|
|
57
|
+
}
|
|
58
|
+
async sendTransaction(tx) {
|
|
59
|
+
if (!this.provider) {
|
|
60
|
+
throw new Error("EmblemEthersWallet requires a provider to send transactions");
|
|
61
|
+
}
|
|
62
|
+
const populated = await this.populateTransaction(tx);
|
|
63
|
+
const signed = await this.signTransaction(populated);
|
|
64
|
+
return await this.provider.broadcastTransaction(signed);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
_EmblemEthersWallet_config = new WeakMap(), _EmblemEthersWallet_vaultId = new WeakMap();
|
|
68
|
+
export async function toEthersWallet(config, provider, infoOverride) {
|
|
69
|
+
const info = infoOverride ?? await fetchVaultInfo(config);
|
|
70
|
+
return new EmblemEthersWallet(info.evmAddress, info.vaultId, config, provider ?? null);
|
|
71
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { EmblemRemoteConfig } from "./types";
|
|
2
|
+
export declare function emblemPost<T = any>(path: string, body: any, { apiKey, baseUrl }: EmblemRemoteConfig): Promise<T>;
|
|
3
|
+
export declare function emblemGet<T = any>(path: string, { apiKey, baseUrl }: EmblemRemoteConfig): Promise<T>;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export async function emblemPost(path, body, { apiKey, baseUrl = "https://api.emblemvault.ai" }) {
|
|
2
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
3
|
+
method: "POST",
|
|
4
|
+
headers: {
|
|
5
|
+
"content-type": "application/json",
|
|
6
|
+
"x-api-key": apiKey,
|
|
7
|
+
},
|
|
8
|
+
body: JSON.stringify(body),
|
|
9
|
+
});
|
|
10
|
+
if (!res.ok) {
|
|
11
|
+
const text = await res.text().catch(() => "");
|
|
12
|
+
throw new Error(`Emblem signer error ${res.status}: ${text || res.statusText}`);
|
|
13
|
+
}
|
|
14
|
+
return res.json();
|
|
15
|
+
}
|
|
16
|
+
export async function emblemGet(path, { apiKey, baseUrl = "https://api.emblemvault.ai" }) {
|
|
17
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
18
|
+
method: "GET",
|
|
19
|
+
headers: {
|
|
20
|
+
"x-api-key": apiKey,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
const text = await res.text().catch(() => "");
|
|
25
|
+
throw new Error(`Emblem signer error ${res.status}: ${text || res.statusText}`);
|
|
26
|
+
}
|
|
27
|
+
return res.json();
|
|
28
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Provider } from "ethers";
|
|
2
|
+
import type { EmblemRemoteConfig } from "./types";
|
|
3
|
+
export type { EmblemRemoteConfig, Hex, VaultInfo } from "./types";
|
|
4
|
+
import { EmblemEthersWallet } from "./ethers";
|
|
5
|
+
import { EmblemSolanaSigner } from "./solana";
|
|
6
|
+
import { EmblemWeb3Adapter } from "./web3";
|
|
7
|
+
export declare class EmblemVaultClient {
|
|
8
|
+
private readonly config;
|
|
9
|
+
private _infoPromise?;
|
|
10
|
+
constructor(config: EmblemRemoteConfig);
|
|
11
|
+
/** Lazily fetch and cache vault info */
|
|
12
|
+
private getInfo;
|
|
13
|
+
toViemAccount(): Promise<{
|
|
14
|
+
address: import("abitype").Address;
|
|
15
|
+
nonceManager?: import("viem").NonceManager | undefined;
|
|
16
|
+
sign?: ((parameters: {
|
|
17
|
+
hash: import("viem").Hash;
|
|
18
|
+
}) => Promise<import("viem").Hex>) | undefined | undefined;
|
|
19
|
+
signAuthorization?: ((parameters: import("viem").AuthorizationRequest) => Promise<import("viem/accounts").SignAuthorizationReturnType>) | undefined | undefined;
|
|
20
|
+
signMessage: ({ message }: {
|
|
21
|
+
message: import("viem").SignableMessage;
|
|
22
|
+
}) => Promise<import("viem").Hex>;
|
|
23
|
+
signTransaction: <serializer extends import("viem").SerializeTransactionFn<import("viem").TransactionSerializable> = import("viem").SerializeTransactionFn<import("viem").TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
|
|
24
|
+
serializer?: serializer | undefined;
|
|
25
|
+
} | undefined) => Promise<import("viem").Hex>;
|
|
26
|
+
signTypedData: <const typedData extends import("abitype").TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: import("viem").TypedDataDefinition<typedData, primaryType>) => Promise<import("viem").Hex>;
|
|
27
|
+
publicKey: import("viem").Hex;
|
|
28
|
+
source: string;
|
|
29
|
+
type: "local";
|
|
30
|
+
}>;
|
|
31
|
+
toEthersWallet(provider?: Provider | null): Promise<EmblemEthersWallet>;
|
|
32
|
+
toSolanaWeb3Signer(): Promise<EmblemSolanaSigner>;
|
|
33
|
+
toSolanaKitSigner(): Promise<EmblemSolanaSigner>;
|
|
34
|
+
toWeb3Adapter(): Promise<EmblemWeb3Adapter>;
|
|
35
|
+
}
|
|
36
|
+
export declare function createEmblemClient(config: EmblemRemoteConfig): EmblemVaultClient;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { toViemAccount } from "./viem";
|
|
2
|
+
import { toEthersWallet } from "./ethers";
|
|
3
|
+
import { toSolanaKitSigner, toSolanaWeb3Signer } from "./solana";
|
|
4
|
+
import { toWeb3Adapter } from "./web3";
|
|
5
|
+
import { fetchVaultInfo } from "./vault";
|
|
6
|
+
export class EmblemVaultClient {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
}
|
|
10
|
+
/** Lazily fetch and cache vault info */
|
|
11
|
+
getInfo() {
|
|
12
|
+
if (!this._infoPromise)
|
|
13
|
+
this._infoPromise = fetchVaultInfo(this.config);
|
|
14
|
+
return this._infoPromise;
|
|
15
|
+
}
|
|
16
|
+
async toViemAccount() {
|
|
17
|
+
const info = await this.getInfo();
|
|
18
|
+
return toViemAccount(this.config, info);
|
|
19
|
+
}
|
|
20
|
+
async toEthersWallet(provider) {
|
|
21
|
+
const info = await this.getInfo();
|
|
22
|
+
return toEthersWallet(this.config, provider ?? null, info);
|
|
23
|
+
}
|
|
24
|
+
async toSolanaWeb3Signer() {
|
|
25
|
+
const info = await this.getInfo();
|
|
26
|
+
return toSolanaWeb3Signer(this.config, info);
|
|
27
|
+
}
|
|
28
|
+
async toSolanaKitSigner() {
|
|
29
|
+
const info = await this.getInfo();
|
|
30
|
+
return toSolanaKitSigner(this.config, info);
|
|
31
|
+
}
|
|
32
|
+
async toWeb3Adapter() {
|
|
33
|
+
const info = await this.getInfo();
|
|
34
|
+
return toWeb3Adapter(this.config, info);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function createEmblemClient(config) {
|
|
38
|
+
return new EmblemVaultClient(config);
|
|
39
|
+
}
|
package/dist/solana.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EmblemRemoteConfig, VaultInfo } from "./types";
|
|
2
|
+
export declare class EmblemSolanaSigner {
|
|
3
|
+
readonly publicKey: string;
|
|
4
|
+
constructor(publicKey: string);
|
|
5
|
+
signMessage(_message: Uint8Array | string): Promise<string>;
|
|
6
|
+
signTransaction(_tx: unknown): Promise<string>;
|
|
7
|
+
}
|
|
8
|
+
export declare function toSolanaWeb3Signer(config: EmblemRemoteConfig, infoOverride?: VaultInfo): Promise<EmblemSolanaSigner>;
|
|
9
|
+
export declare function toSolanaKitSigner(config: EmblemRemoteConfig, infoOverride?: VaultInfo): Promise<EmblemSolanaSigner>;
|
package/dist/solana.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { fetchVaultInfo } from "./vault";
|
|
2
|
+
export class EmblemSolanaSigner {
|
|
3
|
+
constructor(publicKey) {
|
|
4
|
+
this.publicKey = publicKey;
|
|
5
|
+
}
|
|
6
|
+
async signMessage(_message) {
|
|
7
|
+
throw new Error("Solana signing via Emblem is not implemented yet");
|
|
8
|
+
}
|
|
9
|
+
async signTransaction(_tx) {
|
|
10
|
+
throw new Error("Solana transaction signing via Emblem is not implemented yet");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function toSolanaWeb3Signer(config, infoOverride) {
|
|
14
|
+
const info = infoOverride ?? (await fetchVaultInfo(config));
|
|
15
|
+
return new EmblemSolanaSigner(info.address);
|
|
16
|
+
}
|
|
17
|
+
export async function toSolanaKitSigner(config, infoOverride) {
|
|
18
|
+
const info = infoOverride ?? (await fetchVaultInfo(config));
|
|
19
|
+
return new EmblemSolanaSigner(info.address);
|
|
20
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type Hex = `0x${string}`;
|
|
2
|
+
/** Config for Emblem remote signer */
|
|
3
|
+
export type EmblemRemoteConfig = {
|
|
4
|
+
/** x-api-key for your authenticate middleware */
|
|
5
|
+
apiKey: string;
|
|
6
|
+
/** Base URL for the Emblem signer API */
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
};
|
|
9
|
+
export type VaultInfo = {
|
|
10
|
+
vaultId: string;
|
|
11
|
+
address: string;
|
|
12
|
+
evmAddress: `0x${string}`;
|
|
13
|
+
created_by?: string;
|
|
14
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Hex } from "./types";
|
|
2
|
+
export declare function toHexIfBigInt(v: any): any;
|
|
3
|
+
/**
|
|
4
|
+
* viem txs sometimes have bigint / hex / optional fields. Ethers serializers
|
|
5
|
+
* accept hex strings for numeric fields. Normalize where helpful.
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalizeTxForEmblem(tx: any): any;
|
|
8
|
+
export declare function isHexString(value: any): value is Hex;
|
|
9
|
+
export declare function bytesToHex(bytes: ArrayLike<number>): Hex;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function toHexIfBigInt(v) {
|
|
2
|
+
return typeof v === "bigint" ? ("0x" + v.toString(16)) : v;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* viem txs sometimes have bigint / hex / optional fields. Ethers serializers
|
|
6
|
+
* accept hex strings for numeric fields. Normalize where helpful.
|
|
7
|
+
*/
|
|
8
|
+
export function normalizeTxForEmblem(tx) {
|
|
9
|
+
const out = { ...tx };
|
|
10
|
+
if (out.value !== undefined)
|
|
11
|
+
out.value = toHexIfBigInt(out.value);
|
|
12
|
+
if (out.gas !== undefined) {
|
|
13
|
+
out.gasLimit = toHexIfBigInt(out.gas);
|
|
14
|
+
delete out.gas;
|
|
15
|
+
}
|
|
16
|
+
if (out.gasLimit !== undefined)
|
|
17
|
+
out.gasLimit = toHexIfBigInt(out.gasLimit);
|
|
18
|
+
if (out.gasPrice !== undefined)
|
|
19
|
+
out.gasPrice = toHexIfBigInt(out.gasPrice);
|
|
20
|
+
if (out.maxFeePerGas !== undefined)
|
|
21
|
+
out.maxFeePerGas = toHexIfBigInt(out.maxFeePerGas);
|
|
22
|
+
if (out.maxPriorityFeePerGas !== undefined)
|
|
23
|
+
out.maxPriorityFeePerGas = toHexIfBigInt(out.maxPriorityFeePerGas);
|
|
24
|
+
if (out.nonce !== undefined)
|
|
25
|
+
out.nonce = Number(out.nonce);
|
|
26
|
+
if (out.chainId !== undefined)
|
|
27
|
+
out.chainId = Number(out.chainId);
|
|
28
|
+
// Some backends only accept legacy fields; fold EIP-1559 into gasPrice and drop unsupported keys
|
|
29
|
+
if (out.maxFeePerGas !== undefined || out.maxPriorityFeePerGas !== undefined) {
|
|
30
|
+
if (out.gasPrice === undefined && out.maxFeePerGas !== undefined) {
|
|
31
|
+
out.gasPrice = out.maxFeePerGas;
|
|
32
|
+
}
|
|
33
|
+
delete out.maxFeePerGas;
|
|
34
|
+
delete out.maxPriorityFeePerGas;
|
|
35
|
+
}
|
|
36
|
+
// Remove fields commonly unsupported by legacy serializers
|
|
37
|
+
delete out.type;
|
|
38
|
+
delete out.accessList;
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
export function isHexString(value) {
|
|
42
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]*$/.test(value);
|
|
43
|
+
}
|
|
44
|
+
export function bytesToHex(bytes) {
|
|
45
|
+
let out = "0x";
|
|
46
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
47
|
+
out += bytes[i].toString(16).padStart(2, "0");
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
package/dist/vault.d.ts
ADDED
package/dist/vault.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { emblemGet, emblemPost } from "./http";
|
|
2
|
+
export async function fetchVaultInfo(config) {
|
|
3
|
+
let data;
|
|
4
|
+
try {
|
|
5
|
+
data = await emblemGet("/vault/info", config);
|
|
6
|
+
}
|
|
7
|
+
catch (err) {
|
|
8
|
+
// Some environments may require POST for this endpoint; try POST fallback
|
|
9
|
+
data = await emblemPost("/vault/info", {}, config);
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
vaultId: data.vaultId,
|
|
13
|
+
address: data.address,
|
|
14
|
+
evmAddress: data.evmAddress,
|
|
15
|
+
created_by: data.created_by,
|
|
16
|
+
};
|
|
17
|
+
}
|
package/dist/viem.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { EmblemRemoteConfig, VaultInfo } from "./types";
|
|
2
|
+
import type { TypedDataDefinition } from "viem";
|
|
3
|
+
export declare function toViemAccount(config: EmblemRemoteConfig, infoOverride?: VaultInfo): Promise<{
|
|
4
|
+
address: import("abitype").Address;
|
|
5
|
+
nonceManager?: import("viem").NonceManager | undefined;
|
|
6
|
+
sign?: ((parameters: {
|
|
7
|
+
hash: import("viem").Hash;
|
|
8
|
+
}) => Promise<import("viem").Hex>) | undefined | undefined;
|
|
9
|
+
signAuthorization?: ((parameters: import("viem").AuthorizationRequest) => Promise<import("viem/accounts").SignAuthorizationReturnType>) | undefined | undefined;
|
|
10
|
+
signMessage: ({ message }: {
|
|
11
|
+
message: import("viem").SignableMessage;
|
|
12
|
+
}) => Promise<import("viem").Hex>;
|
|
13
|
+
signTransaction: <serializer extends import("viem").SerializeTransactionFn<import("viem").TransactionSerializable> = import("viem").SerializeTransactionFn<import("viem").TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
|
|
14
|
+
serializer?: serializer | undefined;
|
|
15
|
+
} | undefined) => Promise<import("viem").Hex>;
|
|
16
|
+
signTypedData: <const typedData extends import("abitype").TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<import("viem").Hex>;
|
|
17
|
+
publicKey: import("viem").Hex;
|
|
18
|
+
source: string;
|
|
19
|
+
type: "local";
|
|
20
|
+
}>;
|
package/dist/viem.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { emblemPost } from "./http";
|
|
2
|
+
import { bytesToHex, isHexString, normalizeTxForEmblem } from "./utils";
|
|
3
|
+
import { toAccount } from "viem/accounts";
|
|
4
|
+
import { fetchVaultInfo } from "./vault";
|
|
5
|
+
export async function toViemAccount(config, infoOverride) {
|
|
6
|
+
const info = infoOverride ?? await fetchVaultInfo(config);
|
|
7
|
+
const { evmAddress, vaultId } = info;
|
|
8
|
+
return toAccount({
|
|
9
|
+
address: evmAddress,
|
|
10
|
+
async signMessage({ message }) {
|
|
11
|
+
let payload;
|
|
12
|
+
if (typeof message === "string") {
|
|
13
|
+
payload = message;
|
|
14
|
+
}
|
|
15
|
+
else if (message && typeof message.raw !== "undefined") {
|
|
16
|
+
const raw = message.raw;
|
|
17
|
+
payload = typeof raw === "string" ? raw : bytesToHex(raw);
|
|
18
|
+
}
|
|
19
|
+
else if (message instanceof Uint8Array) {
|
|
20
|
+
payload = bytesToHex(message);
|
|
21
|
+
}
|
|
22
|
+
else if (isHexString(message)) {
|
|
23
|
+
payload = message;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
payload = String(message);
|
|
27
|
+
}
|
|
28
|
+
const data = await emblemPost("/sign-eth-message", { vaultId, message: payload }, config);
|
|
29
|
+
return data.signature;
|
|
30
|
+
},
|
|
31
|
+
async signTypedData(typedData) {
|
|
32
|
+
const { domain, types, message } = typedData;
|
|
33
|
+
const data = await emblemPost("/sign-typed-message", { vaultId, domain, types, message }, config);
|
|
34
|
+
return data.signature;
|
|
35
|
+
},
|
|
36
|
+
async signTransaction(tx, _opts) {
|
|
37
|
+
const normalizedTx = normalizeTxForEmblem(tx);
|
|
38
|
+
const data = await emblemPost("/sign-eth-tx", { vaultId, transaction: normalizedTx }, config);
|
|
39
|
+
return data.signedTransaction;
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
package/dist/web3.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EmblemRemoteConfig, Hex, VaultInfo } from "./types";
|
|
2
|
+
export declare class EmblemWeb3Adapter {
|
|
3
|
+
#private;
|
|
4
|
+
readonly address: `0x${string}`;
|
|
5
|
+
constructor(address: `0x${string}`, vaultId: string, config: EmblemRemoteConfig);
|
|
6
|
+
signMessage(message: string | Uint8Array): Promise<Hex>;
|
|
7
|
+
signTypedData(domain: any, types: any, message: any): Promise<Hex>;
|
|
8
|
+
signTransaction(tx: any): Promise<{
|
|
9
|
+
rawTransaction: Hex;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export declare function toWeb3Adapter(config: EmblemRemoteConfig, infoOverride?: VaultInfo): Promise<EmblemWeb3Adapter>;
|
package/dist/web3.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _EmblemWeb3Adapter_vaultId, _EmblemWeb3Adapter_config;
|
|
13
|
+
import { emblemPost } from "./http";
|
|
14
|
+
import { bytesToHex, normalizeTxForEmblem } from "./utils";
|
|
15
|
+
import { fetchVaultInfo } from "./vault";
|
|
16
|
+
export class EmblemWeb3Adapter {
|
|
17
|
+
constructor(address, vaultId, config) {
|
|
18
|
+
_EmblemWeb3Adapter_vaultId.set(this, void 0);
|
|
19
|
+
_EmblemWeb3Adapter_config.set(this, void 0);
|
|
20
|
+
this.address = address;
|
|
21
|
+
__classPrivateFieldSet(this, _EmblemWeb3Adapter_vaultId, vaultId, "f");
|
|
22
|
+
__classPrivateFieldSet(this, _EmblemWeb3Adapter_config, config, "f");
|
|
23
|
+
}
|
|
24
|
+
async signMessage(message) {
|
|
25
|
+
const payload = typeof message === "string" ? message : bytesToHex(message);
|
|
26
|
+
const data = await emblemPost("/sign-eth-message", { vaultId: __classPrivateFieldGet(this, _EmblemWeb3Adapter_vaultId, "f"), message: payload }, __classPrivateFieldGet(this, _EmblemWeb3Adapter_config, "f"));
|
|
27
|
+
return data.signature;
|
|
28
|
+
}
|
|
29
|
+
async signTypedData(domain, types, message) {
|
|
30
|
+
const data = await emblemPost("/sign-typed-message", { vaultId: __classPrivateFieldGet(this, _EmblemWeb3Adapter_vaultId, "f"), domain, types, message }, __classPrivateFieldGet(this, _EmblemWeb3Adapter_config, "f"));
|
|
31
|
+
return data.signature;
|
|
32
|
+
}
|
|
33
|
+
async signTransaction(tx) {
|
|
34
|
+
const normalized = normalizeTxForEmblem(tx);
|
|
35
|
+
const resp = await emblemPost("/sign-eth-tx", { vaultId: __classPrivateFieldGet(this, _EmblemWeb3Adapter_vaultId, "f"), transaction: normalized }, __classPrivateFieldGet(this, _EmblemWeb3Adapter_config, "f"));
|
|
36
|
+
return { rawTransaction: resp.signedTransaction };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
_EmblemWeb3Adapter_vaultId = new WeakMap(), _EmblemWeb3Adapter_config = new WeakMap();
|
|
40
|
+
export async function toWeb3Adapter(config, infoOverride) {
|
|
41
|
+
const info = infoOverride ?? (await fetchVaultInfo(config));
|
|
42
|
+
return new EmblemWeb3Adapter(info.evmAddress, info.vaultId, config);
|
|
43
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "emblem-vault-ai-signers",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Emblem Vault remote signer adapters for viem and ethers",
|
|
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
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"clean": "rm -rf dist",
|
|
21
|
+
"test": "vitest run --reporter=verbose",
|
|
22
|
+
"test:watch": "vitest --reporter=verbose",
|
|
23
|
+
"test:ci": "vitest run --reporter=default",
|
|
24
|
+
"test:integration": "vitest -c vitest.int.config.ts run --reporter=verbose"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"emblem",
|
|
28
|
+
"vault",
|
|
29
|
+
"signer",
|
|
30
|
+
"ethers",
|
|
31
|
+
"viem"
|
|
32
|
+
],
|
|
33
|
+
"author": "",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"ethers": "^6.10.0",
|
|
37
|
+
"viem": "^2.0.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"typescript": "^5.3.3",
|
|
41
|
+
"vitest": "^1.6.0",
|
|
42
|
+
"dotenv": "^16.4.5",
|
|
43
|
+
"ethers": "^6.10.0",
|
|
44
|
+
"viem": "^2.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|