@realmint/sdk 0.1.0 → 0.2.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/README.md +27 -0
- package/dist/agent.d.ts +37 -0
- package/dist/agent.js +64 -0
- package/dist/client.d.ts +6 -1
- package/dist/client.js +39 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +8 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,6 +98,33 @@ await fundWithX402(
|
|
|
98
98
|
// USDC is bridged to the user's Injective smart account, ready for buy().
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
## Keyless agent auth
|
|
102
|
+
|
|
103
|
+
An autonomous agent holding only its own EVM key can authenticate without an API
|
|
104
|
+
key: `agentLogin` exchanges an EOA signature for a bearer and returns a ready
|
|
105
|
+
client.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { agentLogin, fundWithX402, signerFromViemAccount } from "@realmint/sdk";
|
|
109
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
110
|
+
|
|
111
|
+
const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY!);
|
|
112
|
+
const { client } = await agentLogin({
|
|
113
|
+
ownerEoa: account.address,
|
|
114
|
+
signMessage: (m) => account.signMessage({ message: m }),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await fundWithX402(client, { owner_eoa: account.address, amount_usdc: 5, asset_id: "tslax" }, payX402);
|
|
118
|
+
await client.buy(
|
|
119
|
+
{ asset_id: "tslax", source_token: "USDC", amount_in: 5, agent_wallet: account.address },
|
|
120
|
+
signerFromViemAccount(account),
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The bearer only authorizes quoting/creating intents for the EOA's own smart
|
|
125
|
+
accounts; moving funds still requires the EOA's UserOp signature. See the
|
|
126
|
+
[reference agent](https://github.com/realmint-io/realmint-x402-agent).
|
|
127
|
+
|
|
101
128
|
## Signing model
|
|
102
129
|
|
|
103
130
|
`buy()` signs an **ERC-4337 UserOp v0.7** digest as an EIP-191 `personal_sign`
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { RealmintClient } from "./client.js";
|
|
2
|
+
/**
|
|
3
|
+
* Signs an arbitrary text message as an EIP-191 `personal_sign`, returning the
|
|
4
|
+
* 0x signature. Build one from a viem account:
|
|
5
|
+
* (m) => account.signMessage({ message: m })
|
|
6
|
+
* or an EIP-1193 provider:
|
|
7
|
+
* (m) => provider.request({ method: "personal_sign", params: [m, address] })
|
|
8
|
+
*/
|
|
9
|
+
export type SignMessage = (message: string) => Promise<string>;
|
|
10
|
+
export interface AgentLoginOptions {
|
|
11
|
+
/** The agent's EVM EOA — the identity it authenticates as. */
|
|
12
|
+
ownerEoa: string;
|
|
13
|
+
/** EIP-191 personal_sign over the challenge text. */
|
|
14
|
+
signMessage: SignMessage;
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
fetch?: typeof fetch;
|
|
17
|
+
}
|
|
18
|
+
export interface AgentSession {
|
|
19
|
+
/** A `RealmintClient` pre-configured with the minted bearer. */
|
|
20
|
+
client: RealmintClient;
|
|
21
|
+
accessToken: string;
|
|
22
|
+
ownerEoa: string;
|
|
23
|
+
/** Seconds until the bearer expires. */
|
|
24
|
+
expiresIn: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Keyless agent login: exchange an EOA signature for a bearer token — no API
|
|
28
|
+
* key. Requests a challenge, signs it, and returns a ready `RealmintClient`.
|
|
29
|
+
*
|
|
30
|
+
* const { client } = await agentLogin({
|
|
31
|
+
* ownerEoa: account.address,
|
|
32
|
+
* signMessage: (m) => account.signMessage({ message: m }),
|
|
33
|
+
* });
|
|
34
|
+
* await client.buy({ asset_id: "tslax", source_token: "USDC", amount_in: 5,
|
|
35
|
+
* agent_wallet: account.address }, sign);
|
|
36
|
+
*/
|
|
37
|
+
export declare function agentLogin(opts: AgentLoginOptions): Promise<AgentSession>;
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { RealmintClient } from "./client.js";
|
|
2
|
+
const DEFAULT_BASE_URL = "https://api.realmint.io";
|
|
3
|
+
/**
|
|
4
|
+
* Keyless agent login: exchange an EOA signature for a bearer token — no API
|
|
5
|
+
* key. Requests a challenge, signs it, and returns a ready `RealmintClient`.
|
|
6
|
+
*
|
|
7
|
+
* const { client } = await agentLogin({
|
|
8
|
+
* ownerEoa: account.address,
|
|
9
|
+
* signMessage: (m) => account.signMessage({ message: m }),
|
|
10
|
+
* });
|
|
11
|
+
* await client.buy({ asset_id: "tslax", source_token: "USDC", amount_in: 5,
|
|
12
|
+
* agent_wallet: account.address }, sign);
|
|
13
|
+
*/
|
|
14
|
+
export async function agentLogin(opts) {
|
|
15
|
+
const base = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
16
|
+
const f = opts.fetch ?? globalThis.fetch;
|
|
17
|
+
if (!f)
|
|
18
|
+
throw new Error("No fetch available — pass `fetch` in options (Node < 18).");
|
|
19
|
+
const challenge = await postJson(f, `${base}/v1/agent/auth/challenge`, {
|
|
20
|
+
owner_eoa: opts.ownerEoa,
|
|
21
|
+
});
|
|
22
|
+
const message = challenge.message;
|
|
23
|
+
const challengeToken = challenge.challenge_token;
|
|
24
|
+
if (!message || !challengeToken) {
|
|
25
|
+
throw new Error(`Unexpected challenge response: ${JSON.stringify(challenge)}`);
|
|
26
|
+
}
|
|
27
|
+
const signature = await opts.signMessage(message);
|
|
28
|
+
const token = await postJson(f, `${base}/v1/agent/auth/token`, {
|
|
29
|
+
challenge_token: challengeToken,
|
|
30
|
+
signature,
|
|
31
|
+
});
|
|
32
|
+
const accessToken = token.access_token;
|
|
33
|
+
if (!accessToken) {
|
|
34
|
+
throw new Error(`Agent auth failed: ${JSON.stringify(token)}`);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
client: new RealmintClient({ baseUrl: base, bearer: accessToken, fetch: opts.fetch }),
|
|
38
|
+
accessToken,
|
|
39
|
+
ownerEoa: opts.ownerEoa,
|
|
40
|
+
expiresIn: Number(token.expires_in ?? 3600),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async function postJson(f, url, body) {
|
|
44
|
+
const res = await f(url, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { "content-type": "application/json" },
|
|
47
|
+
body: JSON.stringify(body),
|
|
48
|
+
});
|
|
49
|
+
const text = await res.text();
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = text ? JSON.parse(text) : undefined;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
parsed = text;
|
|
56
|
+
}
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
const msg = parsed?.message ??
|
|
59
|
+
parsed?.error ??
|
|
60
|
+
`HTTP ${res.status}`;
|
|
61
|
+
throw new Error(`${url} → ${msg}`);
|
|
62
|
+
}
|
|
63
|
+
return (parsed ?? {});
|
|
64
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface WaitOptions {
|
|
|
15
15
|
*/
|
|
16
16
|
export declare class RealmintClient {
|
|
17
17
|
private readonly baseUrl;
|
|
18
|
-
private readonly
|
|
18
|
+
private readonly authHeaders;
|
|
19
19
|
private readonly fetchImpl;
|
|
20
20
|
constructor(opts: RealmintClientOptions);
|
|
21
21
|
private request;
|
|
@@ -56,6 +56,11 @@ export declare class RealmintClient {
|
|
|
56
56
|
}>;
|
|
57
57
|
/** Submit a signed UserOp directly to the bundler; returns the userOp hash. */
|
|
58
58
|
submitUserOp(bundlerUrl: string, signedUserOp: Record<string, unknown>, entrypoint: string): Promise<Hex>;
|
|
59
|
+
/**
|
|
60
|
+
* Poll the bundler for a UserOp receipt until it's mined; returns the source
|
|
61
|
+
* transaction hash (what `execute` needs). Throws if the UserOp reverts.
|
|
62
|
+
*/
|
|
63
|
+
waitForUserOp(bundlerUrl: string, userOpHash: Hex, opts?: WaitOptions): Promise<string>;
|
|
59
64
|
private bundlerRpc;
|
|
60
65
|
/** Poll the intent until it reaches a terminal state (or times out). */
|
|
61
66
|
waitForSettlement(intentId: string, opts?: WaitOptions): Promise<RouteIntent>;
|
package/dist/client.js
CHANGED
|
@@ -17,11 +17,20 @@ export class RealmintError extends Error {
|
|
|
17
17
|
*/
|
|
18
18
|
export class RealmintClient {
|
|
19
19
|
baseUrl;
|
|
20
|
-
|
|
20
|
+
authHeaders;
|
|
21
21
|
fetchImpl;
|
|
22
22
|
constructor(opts) {
|
|
23
23
|
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
24
|
-
|
|
24
|
+
// Bearer (keyless agent auth) takes precedence; fall back to an API key.
|
|
25
|
+
if (opts.bearer) {
|
|
26
|
+
this.authHeaders = { authorization: `Bearer ${opts.bearer}` };
|
|
27
|
+
}
|
|
28
|
+
else if (opts.apiKey) {
|
|
29
|
+
this.authHeaders = { "x-api-key": opts.apiKey };
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
throw new Error("Provide either `apiKey` or `bearer` in RealmintClient options.");
|
|
33
|
+
}
|
|
25
34
|
this.fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
26
35
|
if (!this.fetchImpl) {
|
|
27
36
|
throw new Error("No fetch available — pass `fetch` in options (Node < 18).");
|
|
@@ -32,7 +41,7 @@ export class RealmintClient {
|
|
|
32
41
|
const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
33
42
|
method,
|
|
34
43
|
headers: {
|
|
35
|
-
|
|
44
|
+
...this.authHeaders,
|
|
36
45
|
...(body !== undefined ? { "content-type": "application/json" } : {}),
|
|
37
46
|
},
|
|
38
47
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
@@ -124,6 +133,28 @@ export class RealmintClient {
|
|
|
124
133
|
]);
|
|
125
134
|
return result;
|
|
126
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Poll the bundler for a UserOp receipt until it's mined; returns the source
|
|
138
|
+
* transaction hash (what `execute` needs). Throws if the UserOp reverts.
|
|
139
|
+
*/
|
|
140
|
+
async waitForUserOp(bundlerUrl, userOpHash, opts = {}) {
|
|
141
|
+
const timeoutMs = opts.timeoutMs ?? 120_000;
|
|
142
|
+
const pollMs = opts.pollMs ?? 2_000;
|
|
143
|
+
const deadline = Date.now() + timeoutMs;
|
|
144
|
+
while (Date.now() < deadline) {
|
|
145
|
+
const receipt = (await this.bundlerRpc(bundlerUrl, "eth_getUserOperationReceipt", [
|
|
146
|
+
userOpHash,
|
|
147
|
+
]));
|
|
148
|
+
if (receipt) {
|
|
149
|
+
if (receipt.success === false) {
|
|
150
|
+
throw new RealmintError("Source UserOp reverted on-chain", 200, receipt);
|
|
151
|
+
}
|
|
152
|
+
return receipt.receipt?.transactionHash ?? userOpHash;
|
|
153
|
+
}
|
|
154
|
+
await sleep(pollMs);
|
|
155
|
+
}
|
|
156
|
+
throw new RealmintError(`Timed out waiting for UserOp ${userOpHash} to be mined`, 408, null);
|
|
157
|
+
}
|
|
127
158
|
async bundlerRpc(bundlerUrl, method, params) {
|
|
128
159
|
const res = await this.fetchImpl(bundlerUrl, {
|
|
129
160
|
method: "POST",
|
|
@@ -175,9 +206,13 @@ export class RealmintClient {
|
|
|
175
206
|
const signedUserOp = { ...prep.source_user_op, signature };
|
|
176
207
|
const entrypoint = prep.entrypoint_address ?? account.entrypoint_address;
|
|
177
208
|
const userOpHash = await this.submitUserOp(account.bundler_url, signedUserOp, entrypoint);
|
|
209
|
+
// `execute` requires the source tx to be MINED (else 400 source_tx_not_mined);
|
|
210
|
+
// a UserOp hash is not a tx hash. Wait for inclusion, then execute with the
|
|
211
|
+
// real source tx hash before the quote expires.
|
|
212
|
+
const sourceTxHash = await this.waitForUserOp(account.bundler_url, userOpHash, opts);
|
|
178
213
|
await this.executeIntent(created.intent_id, {
|
|
179
214
|
agent_wallet: params.agent_wallet,
|
|
180
|
-
source_tx_hash:
|
|
215
|
+
source_tx_hash: sourceTxHash,
|
|
181
216
|
});
|
|
182
217
|
return this.waitForSettlement(created.intent_id, opts);
|
|
183
218
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,5 +3,7 @@ export type { WaitOptions } from "./client.js";
|
|
|
3
3
|
export { signerFromViemAccount, signerFromEip1193 } from "./signing.js";
|
|
4
4
|
export { fundWithX402 } from "./x402.js";
|
|
5
5
|
export type { X402PaymentSigner } from "./x402.js";
|
|
6
|
+
export { agentLogin } from "./agent.js";
|
|
7
|
+
export type { AgentLoginOptions, AgentSession, SignMessage } from "./agent.js";
|
|
6
8
|
export type { AccountStatus, CreateIntentParams, CreateIntentResponse, ExecuteResponse, GetIntentResponse, Hex, PrepareResponse, PrepareSolanaSellResponse, QuoteBreakdown, RealmintClientOptions, RouteIntent, SignSolanaWire, SignUserOpHash, TradeAction, UnsignedUserOp, } from "./types.js";
|
|
7
9
|
export { TERMINAL_STATES } from "./types.js";
|
package/dist/index.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -2,8 +2,14 @@ export type Hex = `0x${string}`;
|
|
|
2
2
|
export interface RealmintClientOptions {
|
|
3
3
|
/** API base URL. Defaults to https://api.realmint.io */
|
|
4
4
|
baseUrl?: string;
|
|
5
|
-
/** Partner API key, sent as `x-api-key`. */
|
|
6
|
-
apiKey
|
|
5
|
+
/** Partner API key, sent as `x-api-key`. Provide this or `bearer`. */
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
/**
|
|
8
|
+
* OAuth bearer token, sent as `Authorization: Bearer`. Mint one keyless from
|
|
9
|
+
* an EOA signature with `agentLogin` — no API key needed. Takes precedence
|
|
10
|
+
* over `apiKey` when both are set.
|
|
11
|
+
*/
|
|
12
|
+
bearer?: string;
|
|
7
13
|
/** Override the global fetch (e.g. a proxy-aware fetch). */
|
|
8
14
|
fetch?: typeof fetch;
|
|
9
15
|
}
|
package/package.json
CHANGED