@realmint/sdk 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 +112 -0
- package/dist/client.d.ts +79 -0
- package/dist/client.js +216 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +4 -0
- package/dist/signing.d.ts +29 -0
- package/dist/signing.js +19 -0
- package/dist/types.d.ts +112 -0
- package/dist/types.js +8 -0
- package/dist/x402.d.ts +19 -0
- package/dist/x402.js +21 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# @realmint/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for the [Realmint](https://realmint.io) API — **list, buy, and
|
|
4
|
+
sell** tokenized real-world assets. Non-custodial: you inject the signer, the
|
|
5
|
+
SDK never holds keys. Gas is sponsored in USDC (no native gas needed), and
|
|
6
|
+
funding is over [x402](https://x402.org).
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @realmint/sdk
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
You bring your own EVM signer (viem account or an EIP-1193 provider). For Solana
|
|
13
|
+
sells you also bring a Solana signer. Node 18+ (native `fetch`).
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { RealmintClient, signerFromViemAccount } from "@realmint/sdk";
|
|
19
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
20
|
+
|
|
21
|
+
const client = new RealmintClient({ apiKey: process.env.REALMINT_API_KEY! });
|
|
22
|
+
|
|
23
|
+
// The user's EVM account (the partner manages this key, not Realmint).
|
|
24
|
+
const account = privateKeyToAccount(userPrivateKey);
|
|
25
|
+
const sign = signerFromViemAccount(account);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## List
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
const assets = await client.listAssets({ roots: true });
|
|
32
|
+
const paxg = await client.getAsset("paxg");
|
|
33
|
+
const score = await client.getScore("paxg");
|
|
34
|
+
const venues = await client.getRouteSupport("paxg");
|
|
35
|
+
const holdings = await client.getPortfolio(account.address);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Buy
|
|
39
|
+
|
|
40
|
+
`buy()` runs the whole lifecycle: create → prepare → **sign the UserOp** →
|
|
41
|
+
submit to the bundler → execute → poll to completion. The user's smart account
|
|
42
|
+
must already hold USDC (fund it with `fundWithX402` first).
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const intent = await client.buy(
|
|
46
|
+
{
|
|
47
|
+
asset_id: "paxg",
|
|
48
|
+
source_token: "USDC",
|
|
49
|
+
amount_in: 100, // 100 USDC
|
|
50
|
+
agent_wallet: account.address,
|
|
51
|
+
},
|
|
52
|
+
sign,
|
|
53
|
+
);
|
|
54
|
+
console.log(intent.status); // "completed"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Sell
|
|
58
|
+
|
|
59
|
+
EVM-deployed assets sell with **zero signatures** (the protocol executes the
|
|
60
|
+
swap via a session key):
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const sold = await client.sellEvm({
|
|
64
|
+
asset_id: "paxg",
|
|
65
|
+
source_token: "USDC",
|
|
66
|
+
amount_in: 0.05, // asset quantity to sell
|
|
67
|
+
agent_wallet: account.address,
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Solana-deployed assets take **one** Solana signature on the prepared wire:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { SignSolanaWire } from "@realmint/sdk";
|
|
75
|
+
|
|
76
|
+
const signWire: SignSolanaWire = async (wireBase64) => {
|
|
77
|
+
// co-sign the base64 V0 transaction with the user's Solana key, return base64
|
|
78
|
+
};
|
|
79
|
+
const sold = await client.sellSolana(
|
|
80
|
+
{ asset_id: "tslax", source_token: "USDC", amount_in: 1, agent_wallet: account.address },
|
|
81
|
+
signWire,
|
|
82
|
+
);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Fund with x402
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { fundWithX402 } from "@realmint/sdk";
|
|
89
|
+
|
|
90
|
+
await fundWithX402(
|
|
91
|
+
client,
|
|
92
|
+
{ owner_eoa: account.address, amount_usdc: 100 },
|
|
93
|
+
async (paymentRequirements) => {
|
|
94
|
+
// settle the requirements with your x402 client (e.g. the Coinbase x402 SDK
|
|
95
|
+
// with a viem account on Base) and return the encoded X-PAYMENT header value
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
// USDC is bridged to the user's Injective smart account, ready for buy().
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Signing model
|
|
102
|
+
|
|
103
|
+
`buy()` signs an **ERC-4337 UserOp v0.7** digest as an EIP-191 `personal_sign`
|
|
104
|
+
over the 32-byte hash — that's what `signerFromViemAccount` /
|
|
105
|
+
`signerFromEip1193` produce. Don't use an EIP-712 typed sign or a raw ECDSA
|
|
106
|
+
sign; the smart account validates the personal_sign form.
|
|
107
|
+
|
|
108
|
+
Lower-level methods (`accountStatus`, `createIntent`, `prepareIntent`,
|
|
109
|
+
`executeIntent`, `getIntent`, `reconcileIntent`, `waitForSettlement`,
|
|
110
|
+
`submitUserOp`) are exposed if you need to drive the lifecycle yourself.
|
|
111
|
+
|
|
112
|
+
See the full API reference at https://api.realmint.io/docs.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { AccountStatus, CreateIntentParams, CreateIntentResponse, ExecuteResponse, GetIntentResponse, Hex, PrepareResponse, PrepareSolanaSellResponse, RealmintClientOptions, RouteIntent, SignSolanaWire, SignUserOpHash } from "./types.js";
|
|
2
|
+
export declare class RealmintError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly body: unknown;
|
|
5
|
+
constructor(message: string, status: number, body: unknown);
|
|
6
|
+
}
|
|
7
|
+
export interface WaitOptions {
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
pollMs?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Client for the Realmint trading API. Non-custodial: signing is done by a
|
|
13
|
+
* caller-supplied signer (see `SignUserOpHash` / `signing.ts`); the SDK never
|
|
14
|
+
* holds keys.
|
|
15
|
+
*/
|
|
16
|
+
export declare class RealmintClient {
|
|
17
|
+
private readonly baseUrl;
|
|
18
|
+
private readonly apiKey;
|
|
19
|
+
private readonly fetchImpl;
|
|
20
|
+
constructor(opts: RealmintClientOptions);
|
|
21
|
+
private request;
|
|
22
|
+
listAssets(params?: {
|
|
23
|
+
roots?: boolean;
|
|
24
|
+
limit?: number;
|
|
25
|
+
offset?: number;
|
|
26
|
+
}): Promise<unknown[]>;
|
|
27
|
+
getAsset(assetId: string): Promise<unknown>;
|
|
28
|
+
getScore(assetId: string): Promise<unknown>;
|
|
29
|
+
getRouteSupport(assetId: string): Promise<unknown>;
|
|
30
|
+
getPortfolio(wallet: string): Promise<unknown>;
|
|
31
|
+
accountStatus(wallet: string): Promise<AccountStatus>;
|
|
32
|
+
createIntent(params: CreateIntentParams): Promise<CreateIntentResponse>;
|
|
33
|
+
prepareIntent(intentId: string): Promise<PrepareResponse>;
|
|
34
|
+
executeIntent(intentId: string, body: {
|
|
35
|
+
agent_wallet: string;
|
|
36
|
+
source_tx_hash?: string;
|
|
37
|
+
min_amount_out?: number;
|
|
38
|
+
}): Promise<ExecuteResponse>;
|
|
39
|
+
reconcileIntent(intentId: string): Promise<unknown>;
|
|
40
|
+
getIntent(intentId: string): Promise<GetIntentResponse>;
|
|
41
|
+
prepareSolanaSell(intentId: string): Promise<PrepareSolanaSellResponse>;
|
|
42
|
+
submitSolanaSell(intentId: string, signedWireBase64: string): Promise<unknown>;
|
|
43
|
+
/**
|
|
44
|
+
* Raw x402 funding call. Without `xPaymentHeader` you get a 402 whose body
|
|
45
|
+
* carries the payment requirements; settle them with an x402 client and call
|
|
46
|
+
* again with the encoded `X-PAYMENT` header. Returns the HTTP status + body
|
|
47
|
+
* so the caller can branch on 402. Public endpoint — no api-key needed.
|
|
48
|
+
*/
|
|
49
|
+
x402Buy(params: {
|
|
50
|
+
owner_eoa: string;
|
|
51
|
+
amount_usdc: number;
|
|
52
|
+
asset_id?: string;
|
|
53
|
+
}, xPaymentHeader?: string): Promise<{
|
|
54
|
+
status: number;
|
|
55
|
+
body: unknown;
|
|
56
|
+
}>;
|
|
57
|
+
/** Submit a signed UserOp directly to the bundler; returns the userOp hash. */
|
|
58
|
+
submitUserOp(bundlerUrl: string, signedUserOp: Record<string, unknown>, entrypoint: string): Promise<Hex>;
|
|
59
|
+
private bundlerRpc;
|
|
60
|
+
/** Poll the intent until it reaches a terminal state (or times out). */
|
|
61
|
+
waitForSettlement(intentId: string, opts?: WaitOptions): Promise<RouteIntent>;
|
|
62
|
+
/**
|
|
63
|
+
* Buy an asset end to end: create → prepare → sign (UserOp v0.7) → submit to
|
|
64
|
+
* the bundler → execute → poll to completion. The user's funds must already
|
|
65
|
+
* be in their smart account (fund via x402 first). Returns the settled intent.
|
|
66
|
+
*/
|
|
67
|
+
buy(params: Omit<CreateIntentParams, "action">, sign: SignUserOpHash, opts?: WaitOptions): Promise<RouteIntent>;
|
|
68
|
+
/**
|
|
69
|
+
* Sell an EVM-deployed asset: 0-signature. Creating the intent is the commit;
|
|
70
|
+
* the protocol executes the source swap via a session key. Returns the
|
|
71
|
+
* settled intent.
|
|
72
|
+
*/
|
|
73
|
+
sellEvm(params: Omit<CreateIntentParams, "action">, opts?: WaitOptions): Promise<RouteIntent>;
|
|
74
|
+
/**
|
|
75
|
+
* Sell a Solana-deployed asset: create → prepare wire → user co-signs → submit
|
|
76
|
+
* → poll. One Solana signature; gas is relayer-sponsored.
|
|
77
|
+
*/
|
|
78
|
+
sellSolana(params: Omit<CreateIntentParams, "action">, signWire: SignSolanaWire, opts?: WaitOptions): Promise<RouteIntent>;
|
|
79
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { TERMINAL_STATES } from "./types.js";
|
|
2
|
+
const DEFAULT_BASE_URL = "https://api.realmint.io";
|
|
3
|
+
export class RealmintError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
body;
|
|
6
|
+
constructor(message, status, body) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.body = body;
|
|
10
|
+
this.name = "RealmintError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Client for the Realmint trading API. Non-custodial: signing is done by a
|
|
15
|
+
* caller-supplied signer (see `SignUserOpHash` / `signing.ts`); the SDK never
|
|
16
|
+
* holds keys.
|
|
17
|
+
*/
|
|
18
|
+
export class RealmintClient {
|
|
19
|
+
baseUrl;
|
|
20
|
+
apiKey;
|
|
21
|
+
fetchImpl;
|
|
22
|
+
constructor(opts) {
|
|
23
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
24
|
+
this.apiKey = opts.apiKey;
|
|
25
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
26
|
+
if (!this.fetchImpl) {
|
|
27
|
+
throw new Error("No fetch available — pass `fetch` in options (Node < 18).");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// ── HTTP ──────────────────────────────────────────────────────────────
|
|
31
|
+
async request(method, path, body) {
|
|
32
|
+
const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
33
|
+
method,
|
|
34
|
+
headers: {
|
|
35
|
+
"x-api-key": this.apiKey,
|
|
36
|
+
...(body !== undefined ? { "content-type": "application/json" } : {}),
|
|
37
|
+
},
|
|
38
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
39
|
+
});
|
|
40
|
+
const text = await res.text();
|
|
41
|
+
const parsed = text ? safeJson(text) : undefined;
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
const msg = parsed?.message ??
|
|
44
|
+
parsed?.error ??
|
|
45
|
+
`Realmint API ${res.status}`;
|
|
46
|
+
throw new RealmintError(msg, res.status, parsed ?? text);
|
|
47
|
+
}
|
|
48
|
+
return parsed;
|
|
49
|
+
}
|
|
50
|
+
// ── Read (list) ───────────────────────────────────────────────────────
|
|
51
|
+
listAssets(params) {
|
|
52
|
+
const q = new URLSearchParams();
|
|
53
|
+
if (params?.roots)
|
|
54
|
+
q.set("roots", "true");
|
|
55
|
+
if (params?.limit != null)
|
|
56
|
+
q.set("limit", String(params.limit));
|
|
57
|
+
if (params?.offset != null)
|
|
58
|
+
q.set("offset", String(params.offset));
|
|
59
|
+
const qs = q.toString();
|
|
60
|
+
return this.request("GET", `/v1/assets/summary${qs ? `?${qs}` : ""}`);
|
|
61
|
+
}
|
|
62
|
+
getAsset(assetId) {
|
|
63
|
+
return this.request("GET", `/v1/assets/${encodeURIComponent(assetId)}`);
|
|
64
|
+
}
|
|
65
|
+
getScore(assetId) {
|
|
66
|
+
return this.request("GET", `/v1/assets/${encodeURIComponent(assetId)}/score`);
|
|
67
|
+
}
|
|
68
|
+
getRouteSupport(assetId) {
|
|
69
|
+
return this.request("GET", `/v1/assets/${encodeURIComponent(assetId)}/route-support`);
|
|
70
|
+
}
|
|
71
|
+
getPortfolio(wallet) {
|
|
72
|
+
return this.request("GET", `/v1/portfolio/${encodeURIComponent(wallet)}`);
|
|
73
|
+
}
|
|
74
|
+
// ── Routing primitives ────────────────────────────────────────────────
|
|
75
|
+
accountStatus(wallet) {
|
|
76
|
+
return this.request("GET", `/v1/account/status?wallet=${encodeURIComponent(wallet)}`);
|
|
77
|
+
}
|
|
78
|
+
createIntent(params) {
|
|
79
|
+
return this.request("POST", "/v1/route/intent", params);
|
|
80
|
+
}
|
|
81
|
+
prepareIntent(intentId) {
|
|
82
|
+
return this.request("POST", `/v1/route/intent/${intentId}/prepare`);
|
|
83
|
+
}
|
|
84
|
+
executeIntent(intentId, body) {
|
|
85
|
+
return this.request("POST", `/v1/route/intent/${intentId}/execute`, body);
|
|
86
|
+
}
|
|
87
|
+
reconcileIntent(intentId) {
|
|
88
|
+
return this.request("POST", `/v1/route/intent/${intentId}/reconcile`);
|
|
89
|
+
}
|
|
90
|
+
getIntent(intentId) {
|
|
91
|
+
return this.request("GET", `/v1/route/intent/${intentId}`);
|
|
92
|
+
}
|
|
93
|
+
prepareSolanaSell(intentId) {
|
|
94
|
+
return this.request("POST", `/v1/route/intent/${intentId}/solana-sell/prepare`);
|
|
95
|
+
}
|
|
96
|
+
submitSolanaSell(intentId, signedWireBase64) {
|
|
97
|
+
return this.request("POST", `/v1/route/intent/${intentId}/solana-sell/submit`, { transaction: signedWireBase64 });
|
|
98
|
+
}
|
|
99
|
+
// ── x402 funding ──────────────────────────────────────────────────────
|
|
100
|
+
/**
|
|
101
|
+
* Raw x402 funding call. Without `xPaymentHeader` you get a 402 whose body
|
|
102
|
+
* carries the payment requirements; settle them with an x402 client and call
|
|
103
|
+
* again with the encoded `X-PAYMENT` header. Returns the HTTP status + body
|
|
104
|
+
* so the caller can branch on 402. Public endpoint — no api-key needed.
|
|
105
|
+
*/
|
|
106
|
+
async x402Buy(params, xPaymentHeader) {
|
|
107
|
+
const res = await this.fetchImpl(`${this.baseUrl}/v1/route/x402-buy`, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
"content-type": "application/json",
|
|
111
|
+
...(xPaymentHeader ? { "X-PAYMENT": xPaymentHeader } : {}),
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify(params),
|
|
114
|
+
});
|
|
115
|
+
const text = await res.text();
|
|
116
|
+
return { status: res.status, body: text ? safeJson(text) : undefined };
|
|
117
|
+
}
|
|
118
|
+
// ── Bundler (ERC-4337) ────────────────────────────────────────────────
|
|
119
|
+
/** Submit a signed UserOp directly to the bundler; returns the userOp hash. */
|
|
120
|
+
async submitUserOp(bundlerUrl, signedUserOp, entrypoint) {
|
|
121
|
+
const result = await this.bundlerRpc(bundlerUrl, "eth_sendUserOperation", [
|
|
122
|
+
signedUserOp,
|
|
123
|
+
entrypoint,
|
|
124
|
+
]);
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
async bundlerRpc(bundlerUrl, method, params) {
|
|
128
|
+
const res = await this.fetchImpl(bundlerUrl, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: { "content-type": "application/json" },
|
|
131
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
132
|
+
});
|
|
133
|
+
const json = (await res.json().catch(() => null));
|
|
134
|
+
if (!res.ok || json?.error) {
|
|
135
|
+
throw new Error(`Bundler error: ${json?.error?.message ?? res.status}`);
|
|
136
|
+
}
|
|
137
|
+
return json?.result;
|
|
138
|
+
}
|
|
139
|
+
// ── Polling ───────────────────────────────────────────────────────────
|
|
140
|
+
/** Poll the intent until it reaches a terminal state (or times out). */
|
|
141
|
+
async waitForSettlement(intentId, opts = {}) {
|
|
142
|
+
const timeoutMs = opts.timeoutMs ?? 180_000;
|
|
143
|
+
const pollMs = opts.pollMs ?? 3_000;
|
|
144
|
+
const deadline = Date.now() + timeoutMs;
|
|
145
|
+
let last;
|
|
146
|
+
while (Date.now() < deadline) {
|
|
147
|
+
// A reconcile nudge advances bridge/destination stages between polls.
|
|
148
|
+
await this.reconcileIntent(intentId).catch(() => undefined);
|
|
149
|
+
const { intent } = await this.getIntent(intentId);
|
|
150
|
+
last = intent;
|
|
151
|
+
if (TERMINAL_STATES.includes(intent.status)) {
|
|
152
|
+
if (intent.status !== "completed") {
|
|
153
|
+
throw new RealmintError(`Intent ${intentId} ended in ${intent.status}${intent.last_error ? `: ${intent.last_error}` : ""}`, 200, intent);
|
|
154
|
+
}
|
|
155
|
+
return intent;
|
|
156
|
+
}
|
|
157
|
+
await sleep(pollMs);
|
|
158
|
+
}
|
|
159
|
+
throw new RealmintError(`Timed out waiting for intent ${intentId} (last status: ${last?.status ?? "unknown"})`, 408, last);
|
|
160
|
+
}
|
|
161
|
+
// ── High-level flows ──────────────────────────────────────────────────
|
|
162
|
+
/**
|
|
163
|
+
* Buy an asset end to end: create → prepare → sign (UserOp v0.7) → submit to
|
|
164
|
+
* the bundler → execute → poll to completion. The user's funds must already
|
|
165
|
+
* be in their smart account (fund via x402 first). Returns the settled intent.
|
|
166
|
+
*/
|
|
167
|
+
async buy(params, sign, opts = {}) {
|
|
168
|
+
const account = await this.accountStatus(params.agent_wallet);
|
|
169
|
+
const created = await this.createIntent({ ...params, action: "buy" });
|
|
170
|
+
const prep = await this.prepareIntent(created.intent_id);
|
|
171
|
+
if (!prep.source_user_op || !prep.source_user_op_hash) {
|
|
172
|
+
throw new RealmintError("This route uses a non-UserOp lane the SDK doesn't sign yet (e.g. deBridge). Use the raw API.", 501, prep);
|
|
173
|
+
}
|
|
174
|
+
const signature = await sign(prep.source_user_op_hash);
|
|
175
|
+
const signedUserOp = { ...prep.source_user_op, signature };
|
|
176
|
+
const entrypoint = prep.entrypoint_address ?? account.entrypoint_address;
|
|
177
|
+
const userOpHash = await this.submitUserOp(account.bundler_url, signedUserOp, entrypoint);
|
|
178
|
+
await this.executeIntent(created.intent_id, {
|
|
179
|
+
agent_wallet: params.agent_wallet,
|
|
180
|
+
source_tx_hash: userOpHash,
|
|
181
|
+
});
|
|
182
|
+
return this.waitForSettlement(created.intent_id, opts);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Sell an EVM-deployed asset: 0-signature. Creating the intent is the commit;
|
|
186
|
+
* the protocol executes the source swap via a session key. Returns the
|
|
187
|
+
* settled intent.
|
|
188
|
+
*/
|
|
189
|
+
async sellEvm(params, opts = {}) {
|
|
190
|
+
const created = await this.createIntent({ ...params, action: "sell" });
|
|
191
|
+
await this.executeIntent(created.intent_id, { agent_wallet: params.agent_wallet }).catch(() => undefined);
|
|
192
|
+
return this.waitForSettlement(created.intent_id, opts);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Sell a Solana-deployed asset: create → prepare wire → user co-signs → submit
|
|
196
|
+
* → poll. One Solana signature; gas is relayer-sponsored.
|
|
197
|
+
*/
|
|
198
|
+
async sellSolana(params, signWire, opts = {}) {
|
|
199
|
+
const created = await this.createIntent({ ...params, action: "sell" });
|
|
200
|
+
const prep = await this.prepareSolanaSell(created.intent_id);
|
|
201
|
+
const signed = await signWire(prep.unsigned_tx_wire_base64);
|
|
202
|
+
await this.submitSolanaSell(created.intent_id, signed);
|
|
203
|
+
return this.waitForSettlement(created.intent_id, opts);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function safeJson(text) {
|
|
207
|
+
try {
|
|
208
|
+
return JSON.parse(text);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return text;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function sleep(ms) {
|
|
215
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
216
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { RealmintClient, RealmintError } from "./client.js";
|
|
2
|
+
export type { WaitOptions } from "./client.js";
|
|
3
|
+
export { signerFromViemAccount, signerFromEip1193 } from "./signing.js";
|
|
4
|
+
export { fundWithX402 } from "./x402.js";
|
|
5
|
+
export type { X402PaymentSigner } from "./x402.js";
|
|
6
|
+
export type { AccountStatus, CreateIntentParams, CreateIntentResponse, ExecuteResponse, GetIntentResponse, Hex, PrepareResponse, PrepareSolanaSellResponse, QuoteBreakdown, RealmintClientOptions, RouteIntent, SignSolanaWire, SignUserOpHash, TradeAction, UnsignedUserOp, } from "./types.js";
|
|
7
|
+
export { TERMINAL_STATES } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Hex, SignUserOpHash } from "./types.js";
|
|
2
|
+
/** Structural shape of a viem `LocalAccount` — avoids a hard viem dependency. */
|
|
3
|
+
interface ViemLikeAccount {
|
|
4
|
+
signMessage(args: {
|
|
5
|
+
message: {
|
|
6
|
+
raw: Hex;
|
|
7
|
+
};
|
|
8
|
+
}): Promise<string>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Build a UserOp signer from a viem account. `signMessage({ message: { raw } })`
|
|
12
|
+
* is an EIP-191 personal_sign over the raw 32-byte hash — exactly what the
|
|
13
|
+
* smart account validates.
|
|
14
|
+
*/
|
|
15
|
+
export declare function signerFromViemAccount(account: ViemLikeAccount): SignUserOpHash;
|
|
16
|
+
/** Structural shape of an EIP-1193 provider (window.ethereum / Privy). */
|
|
17
|
+
interface Eip1193Provider {
|
|
18
|
+
request(args: {
|
|
19
|
+
method: string;
|
|
20
|
+
params: unknown[];
|
|
21
|
+
}): Promise<unknown>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build a UserOp signer from an EIP-1193 provider. Uses `personal_sign`, which
|
|
25
|
+
* the provider treats as an EIP-191 sign over the hash bytes — matching the
|
|
26
|
+
* Realmint web client.
|
|
27
|
+
*/
|
|
28
|
+
export declare function signerFromEip1193(provider: Eip1193Provider, address: string): SignUserOpHash;
|
|
29
|
+
export {};
|
package/dist/signing.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a UserOp signer from a viem account. `signMessage({ message: { raw } })`
|
|
3
|
+
* is an EIP-191 personal_sign over the raw 32-byte hash — exactly what the
|
|
4
|
+
* smart account validates.
|
|
5
|
+
*/
|
|
6
|
+
export function signerFromViemAccount(account) {
|
|
7
|
+
return (hash) => account.signMessage({ message: { raw: hash } });
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Build a UserOp signer from an EIP-1193 provider. Uses `personal_sign`, which
|
|
11
|
+
* the provider treats as an EIP-191 sign over the hash bytes — matching the
|
|
12
|
+
* Realmint web client.
|
|
13
|
+
*/
|
|
14
|
+
export function signerFromEip1193(provider, address) {
|
|
15
|
+
return async (hash) => (await provider.request({
|
|
16
|
+
method: "personal_sign",
|
|
17
|
+
params: [hash, address],
|
|
18
|
+
}));
|
|
19
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export type Hex = `0x${string}`;
|
|
2
|
+
export interface RealmintClientOptions {
|
|
3
|
+
/** API base URL. Defaults to https://api.realmint.io */
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
/** Partner API key, sent as `x-api-key`. */
|
|
6
|
+
apiKey: string;
|
|
7
|
+
/** Override the global fetch (e.g. a proxy-aware fetch). */
|
|
8
|
+
fetch?: typeof fetch;
|
|
9
|
+
}
|
|
10
|
+
export interface AccountStatus {
|
|
11
|
+
wallet: string;
|
|
12
|
+
smart_account: string;
|
|
13
|
+
smart_account_deployed: boolean;
|
|
14
|
+
bundler_url: string;
|
|
15
|
+
entrypoint_address: string;
|
|
16
|
+
readiness_state: string;
|
|
17
|
+
one_click_source_tokens: string[];
|
|
18
|
+
degraded_reason: string | null;
|
|
19
|
+
}
|
|
20
|
+
export type TradeAction = "buy" | "sell";
|
|
21
|
+
export interface CreateIntentParams {
|
|
22
|
+
asset_id: string;
|
|
23
|
+
action: TradeAction;
|
|
24
|
+
/** Funding token on the source chain — "USDC" for the standard rail. */
|
|
25
|
+
source_token: string;
|
|
26
|
+
/** Human units (e.g. 100.0 USDC for a buy; asset quantity for a sell). */
|
|
27
|
+
amount_in: number;
|
|
28
|
+
/** The user's EVM address. Realmint derives their smart account from it. */
|
|
29
|
+
agent_wallet: string;
|
|
30
|
+
destination_chain_id?: number;
|
|
31
|
+
destination_token?: string;
|
|
32
|
+
min_amount_out?: number;
|
|
33
|
+
slippage_bps?: number;
|
|
34
|
+
/** Return a quote without persisting an intent. */
|
|
35
|
+
quote_only?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface QuoteBreakdown {
|
|
38
|
+
source_token: string;
|
|
39
|
+
net_amount_out: number;
|
|
40
|
+
total_cost_usd: number;
|
|
41
|
+
confidence: string;
|
|
42
|
+
expires_at: string;
|
|
43
|
+
[k: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
export interface CreateIntentResponse {
|
|
46
|
+
intent_id: string;
|
|
47
|
+
quote_id: string;
|
|
48
|
+
status: string;
|
|
49
|
+
quote: QuoteBreakdown;
|
|
50
|
+
expires_in_seconds: number;
|
|
51
|
+
}
|
|
52
|
+
/** ERC-4337 v0.7 UserOp as returned by /prepare; `signature` is empty. */
|
|
53
|
+
export interface UnsignedUserOp {
|
|
54
|
+
sender: string;
|
|
55
|
+
nonce: string;
|
|
56
|
+
signature: string;
|
|
57
|
+
[k: string]: unknown;
|
|
58
|
+
}
|
|
59
|
+
export interface PrepareResponse {
|
|
60
|
+
intent_id: string;
|
|
61
|
+
/** Present for the CCTP / UserOp lanes — the SDK's buy path. */
|
|
62
|
+
source_user_op?: UnsignedUserOp;
|
|
63
|
+
/** EIP-712 UserOp digest the user signs (personal_sign / EIP-191). */
|
|
64
|
+
source_user_op_hash?: Hex;
|
|
65
|
+
entrypoint_address?: string;
|
|
66
|
+
smart_account_address?: string;
|
|
67
|
+
/** Present for non-UserOp lanes (e.g. deBridge) — not handled by the SDK yet. */
|
|
68
|
+
unsigned_tx?: Record<string, unknown>;
|
|
69
|
+
bridge_provider?: string;
|
|
70
|
+
}
|
|
71
|
+
export interface ExecuteResponse {
|
|
72
|
+
intent_id: string;
|
|
73
|
+
status: string;
|
|
74
|
+
source_tx_hash: string | null;
|
|
75
|
+
message: string;
|
|
76
|
+
}
|
|
77
|
+
export interface RouteIntent {
|
|
78
|
+
id: string;
|
|
79
|
+
asset_id: string;
|
|
80
|
+
action: string;
|
|
81
|
+
status: string;
|
|
82
|
+
agent_wallet: string;
|
|
83
|
+
source_tx_hash: string | null;
|
|
84
|
+
destination_tx_hash: string | null;
|
|
85
|
+
last_error: string | null;
|
|
86
|
+
[k: string]: unknown;
|
|
87
|
+
}
|
|
88
|
+
export interface GetIntentResponse {
|
|
89
|
+
intent: RouteIntent;
|
|
90
|
+
steps: unknown[];
|
|
91
|
+
}
|
|
92
|
+
export interface PrepareSolanaSellResponse {
|
|
93
|
+
intent_id: string;
|
|
94
|
+
/** Base64 V0 transaction wire, partially signed; the user co-signs it. */
|
|
95
|
+
unsigned_tx_wire_base64: string;
|
|
96
|
+
min_usdc_out: number | string;
|
|
97
|
+
expected_signer: string;
|
|
98
|
+
}
|
|
99
|
+
/** Terminal-ish states the SDK's waiters stop on. */
|
|
100
|
+
export declare const TERMINAL_STATES: readonly ["completed", "terminal_failed", "expired", "cancelled"];
|
|
101
|
+
/**
|
|
102
|
+
* Signs an ERC-4337 UserOp digest the way the smart account expects: an
|
|
103
|
+
* EIP-191 `personal_sign` over the 32-byte hash (NOT an EIP-712 typed sign and
|
|
104
|
+
* NOT a raw ECDSA sign). Return the 0x-prefixed signature.
|
|
105
|
+
*
|
|
106
|
+
* Build one with the helpers in `signing.ts`, or inline:
|
|
107
|
+
* viem: (hash) => account.signMessage({ message: { raw: hash } })
|
|
108
|
+
* EIP-1193: (hash) => provider.request({ method: "personal_sign", params: [hash, address] })
|
|
109
|
+
*/
|
|
110
|
+
export type SignUserOpHash = (userOpHash: Hex) => Promise<string>;
|
|
111
|
+
/** Signs a base64 Solana transaction wire, returning the co-signed wire. */
|
|
112
|
+
export type SignSolanaWire = (unsignedWireBase64: string) => Promise<string>;
|
package/dist/types.js
ADDED
package/dist/x402.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RealmintClient } from "./client.js";
|
|
2
|
+
/**
|
|
3
|
+
* Given the 402 payment-requirements body, produce the encoded `X-PAYMENT`
|
|
4
|
+
* header value. Implement this with an x402 client (e.g. the Coinbase x402 SDK
|
|
5
|
+
* with a viem account on Base) — the SDK stays unopinionated about which one.
|
|
6
|
+
*/
|
|
7
|
+
export type X402PaymentSigner = (paymentRequirements: unknown) => Promise<string>;
|
|
8
|
+
/**
|
|
9
|
+
* Fund a user's smart account in USDC over x402: request the challenge, have
|
|
10
|
+
* `paySigner` settle it, retry with the `X-PAYMENT` header. Realmint then
|
|
11
|
+
* bridges the USDC to the user's Injective smart account, ready for `buy()`.
|
|
12
|
+
*
|
|
13
|
+
* @returns the final settlement body from the API.
|
|
14
|
+
*/
|
|
15
|
+
export declare function fundWithX402(client: RealmintClient, params: {
|
|
16
|
+
owner_eoa: string;
|
|
17
|
+
amount_usdc: number;
|
|
18
|
+
asset_id?: string;
|
|
19
|
+
}, paySigner: X402PaymentSigner): Promise<unknown>;
|
package/dist/x402.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fund a user's smart account in USDC over x402: request the challenge, have
|
|
3
|
+
* `paySigner` settle it, retry with the `X-PAYMENT` header. Realmint then
|
|
4
|
+
* bridges the USDC to the user's Injective smart account, ready for `buy()`.
|
|
5
|
+
*
|
|
6
|
+
* @returns the final settlement body from the API.
|
|
7
|
+
*/
|
|
8
|
+
export async function fundWithX402(client, params, paySigner) {
|
|
9
|
+
const challenge = await client.x402Buy(params);
|
|
10
|
+
if (challenge.status !== 402) {
|
|
11
|
+
if (challenge.status >= 200 && challenge.status < 300)
|
|
12
|
+
return challenge.body;
|
|
13
|
+
throw new Error(`Expected a 402 funding challenge, got ${challenge.status}: ${JSON.stringify(challenge.body)}`);
|
|
14
|
+
}
|
|
15
|
+
const xPayment = await paySigner(challenge.body);
|
|
16
|
+
const settled = await client.x402Buy(params, xPayment);
|
|
17
|
+
if (settled.status < 200 || settled.status >= 300) {
|
|
18
|
+
throw new Error(`x402 settlement failed (${settled.status}): ${JSON.stringify(settled.body)}`);
|
|
19
|
+
}
|
|
20
|
+
return settled.body;
|
|
21
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@realmint/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript SDK for the Realmint API — list, buy, and sell tokenized RWAs (non-custodial; the partner's signer signs).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./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
|
+
"keywords": [
|
|
19
|
+
"realmint",
|
|
20
|
+
"rwa",
|
|
21
|
+
"defi",
|
|
22
|
+
"erc-4337",
|
|
23
|
+
"x402"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"typescript": "^5.4.0"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.json",
|
|
33
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
34
|
+
}
|
|
35
|
+
}
|