@fastnear/intents 1.6.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 +249 -0
- package/dist/cjs/index.cjs +524 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +209 -0
- package/dist/cjs/node.cjs +188 -0
- package/dist/cjs/node.cjs.map +1 -0
- package/dist/cjs/node.d.cts +21 -0
- package/dist/cjs/relay.cjs +127 -0
- package/dist/cjs/relay.cjs.map +1 -0
- package/dist/cjs/relay.d.cts +40 -0
- package/dist/cjs/types-BQpqppU6.d.cts +343 -0
- package/dist/esm/index.d.ts +209 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/node.d.ts +21 -0
- package/dist/esm/node.js +76 -0
- package/dist/esm/node.js.map +1 -0
- package/dist/esm/one-click.js +84 -0
- package/dist/esm/one-click.js.map +1 -0
- package/dist/esm/relay.d.ts +40 -0
- package/dist/esm/relay.js +85 -0
- package/dist/esm/relay.js.map +1 -0
- package/dist/esm/signing.js +198 -0
- package/dist/esm/signing.js.map +1 -0
- package/dist/esm/types-BQpqppU6.d.ts +343 -0
- package/dist/esm/types.js +11 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/verifier.js +124 -0
- package/dist/esm/verifier.js.map +1 -0
- package/dist/umd/browser.global.js +802 -0
- package/dist/umd/browser.global.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { i as IntentSigner } from './types-BQpqppU6.js';
|
|
2
|
+
|
|
3
|
+
interface LocalIntentSignerOptions {
|
|
4
|
+
/** The account whose intents this signer authorizes. */
|
|
5
|
+
accountId: string;
|
|
6
|
+
/**
|
|
7
|
+
* A FULL-ACCESS private key for that account ("ed25519:<base58>").
|
|
8
|
+
* NEP-413 forbids FunctionCall-access keys, and the verifier checks the
|
|
9
|
+
* public key is authorized for the account (registered via add_public_key
|
|
10
|
+
* for named accounts, or matching an implicit account id).
|
|
11
|
+
*/
|
|
12
|
+
privateKey: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Sign intent messages locally with a raw NEAR key — the server/agent
|
|
16
|
+
* counterpart of createWalletIntentSigner. Keep the private key in
|
|
17
|
+
* server-side secret storage; never ship it to a browser.
|
|
18
|
+
*/
|
|
19
|
+
declare function createLocalIntentSigner({ accountId, privateKey, }: LocalIntentSignerOptions): IntentSigner;
|
|
20
|
+
|
|
21
|
+
export { type LocalIntentSignerOptions, createLocalIntentSigner };
|
package/dist/esm/node.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* ⋈ 🏃🏻💨 FastNear intents - ESM (@fastnear/intents version 1.6.0) */
|
|
2
|
+
/* https://www.npmjs.com/package/@fastnear/intents/v/1.6.0 */
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
|
+
import {
|
|
6
|
+
decodeNearPrivateKey,
|
|
7
|
+
publicKeyFromPrivate,
|
|
8
|
+
signNep413Message
|
|
9
|
+
} from "@fastnear/utils";
|
|
10
|
+
import {
|
|
11
|
+
buildIntentMessage,
|
|
12
|
+
randomNonce,
|
|
13
|
+
toSignedIntent,
|
|
14
|
+
unsignedPayloadParts
|
|
15
|
+
} from "./signing.js";
|
|
16
|
+
import {
|
|
17
|
+
INTENTS_CONTRACT_ID
|
|
18
|
+
} from "./types.js";
|
|
19
|
+
function createLocalIntentSigner({
|
|
20
|
+
accountId,
|
|
21
|
+
privateKey
|
|
22
|
+
}) {
|
|
23
|
+
if (!accountId) throw new Error("accountId is required");
|
|
24
|
+
decodeNearPrivateKey(privateKey);
|
|
25
|
+
const publicKey = publicKeyFromPrivate(privateKey);
|
|
26
|
+
function signParts({
|
|
27
|
+
message,
|
|
28
|
+
nonce,
|
|
29
|
+
recipient,
|
|
30
|
+
callbackUrl
|
|
31
|
+
}) {
|
|
32
|
+
const { signature } = signNep413Message(
|
|
33
|
+
{ message, nonce, recipient, callbackUrl },
|
|
34
|
+
privateKey
|
|
35
|
+
);
|
|
36
|
+
return toSignedIntent({
|
|
37
|
+
message,
|
|
38
|
+
nonce,
|
|
39
|
+
recipient,
|
|
40
|
+
publicKey,
|
|
41
|
+
signature,
|
|
42
|
+
callbackUrl
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
__name(signParts, "signParts");
|
|
46
|
+
return {
|
|
47
|
+
async signIntents(params) {
|
|
48
|
+
const signerId = params.signerId ?? accountId;
|
|
49
|
+
const message = buildIntentMessage({
|
|
50
|
+
signerId,
|
|
51
|
+
intents: params.intents,
|
|
52
|
+
deadline: params.deadline
|
|
53
|
+
});
|
|
54
|
+
return signParts({
|
|
55
|
+
message: JSON.stringify(message),
|
|
56
|
+
nonce: params.nonce ?? randomNonce(),
|
|
57
|
+
recipient: params.verifyingContract ?? INTENTS_CONTRACT_ID
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
async signPayload(payload, options) {
|
|
61
|
+
const parts = unsignedPayloadParts(payload);
|
|
62
|
+
const expected = options?.expectedRecipient ?? INTENTS_CONTRACT_ID;
|
|
63
|
+
if (parts.recipient !== expected) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Refusing to sign a payload for recipient "${parts.recipient}" \u2014 expected "${expected}". Pass { expectedRecipient } only when intentionally targeting a different verifier.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return signParts(parts);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
__name(createLocalIntentSigner, "createLocalIntentSigner");
|
|
73
|
+
export {
|
|
74
|
+
createLocalIntentSigner
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/node.ts"],"sourcesContent":["import {\n decodeNearPrivateKey,\n publicKeyFromPrivate,\n signNep413Message,\n} from \"@fastnear/utils\";\nimport {\n buildIntentMessage,\n randomNonce,\n toSignedIntent,\n unsignedPayloadParts,\n} from \"./signing.js\";\nimport {\n INTENTS_CONTRACT_ID,\n type GeneratedUnsignedIntent,\n type IntentSigner,\n type SignIntentsParams,\n type SignPayloadOptions,\n type SignedIntentNep413,\n} from \"./types.js\";\n\nexport interface LocalIntentSignerOptions {\n /** The account whose intents this signer authorizes. */\n accountId: string;\n /**\n * A FULL-ACCESS private key for that account (\"ed25519:<base58>\").\n * NEP-413 forbids FunctionCall-access keys, and the verifier checks the\n * public key is authorized for the account (registered via add_public_key\n * for named accounts, or matching an implicit account id).\n */\n privateKey: string;\n}\n\n/**\n * Sign intent messages locally with a raw NEAR key — the server/agent\n * counterpart of createWalletIntentSigner. Keep the private key in\n * server-side secret storage; never ship it to a browser.\n */\nexport function createLocalIntentSigner({\n accountId,\n privateKey,\n}: LocalIntentSignerOptions): IntentSigner {\n if (!accountId) throw new Error(\"accountId is required\");\n // Validates encoding and length up front (throws on malformed keys).\n decodeNearPrivateKey(privateKey);\n const publicKey = publicKeyFromPrivate(privateKey);\n\n function signParts({\n message,\n nonce,\n recipient,\n callbackUrl,\n }: {\n message: string;\n nonce: Uint8Array;\n recipient: string;\n callbackUrl?: string;\n }): SignedIntentNep413 {\n const { signature } = signNep413Message(\n { message, nonce, recipient, callbackUrl },\n privateKey,\n );\n return toSignedIntent({\n message,\n nonce,\n recipient,\n publicKey,\n signature,\n callbackUrl,\n });\n }\n\n return {\n async signIntents(params: SignIntentsParams): Promise<SignedIntentNep413> {\n const signerId = params.signerId ?? accountId;\n const message = buildIntentMessage({\n signerId,\n intents: params.intents,\n deadline: params.deadline,\n });\n return signParts({\n message: JSON.stringify(message),\n nonce: params.nonce ?? randomNonce(),\n recipient: params.verifyingContract ?? INTENTS_CONTRACT_ID,\n });\n },\n\n async signPayload(\n payload: GeneratedUnsignedIntent,\n options?: SignPayloadOptions,\n ): Promise<SignedIntentNep413> {\n const parts = unsignedPayloadParts(payload);\n const expected = options?.expectedRecipient ?? INTENTS_CONTRACT_ID;\n if (parts.recipient !== expected) {\n throw new Error(\n `Refusing to sign a payload for recipient \"${parts.recipient}\" — expected \"${expected}\". ` +\n \"Pass { expectedRecipient } only when intentionally targeting a different verifier.\",\n );\n }\n return signParts(parts);\n },\n };\n}\n"],"mappings":";;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OAMK;AAmBA,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AACF,GAA2C;AACzC,MAAI,CAAC,UAAW,OAAM,IAAI,MAAM,uBAAuB;AAEvD,uBAAqB,UAAU;AAC/B,QAAM,YAAY,qBAAqB,UAAU;AAEjD,WAAS,UAAU;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAKuB;AACrB,UAAM,EAAE,UAAU,IAAI;AAAA,MACpB,EAAE,SAAS,OAAO,WAAW,YAAY;AAAA,MACzC;AAAA,IACF;AACA,WAAO,eAAe;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAvBS;AAyBT,SAAO;AAAA,IACL,MAAM,YAAY,QAAwD;AACxE,YAAM,WAAW,OAAO,YAAY;AACpC,YAAM,UAAU,mBAAmB;AAAA,QACjC;AAAA,QACA,SAAS,OAAO;AAAA,QAChB,UAAU,OAAO;AAAA,MACnB,CAAC;AACD,aAAO,UAAU;AAAA,QACf,SAAS,KAAK,UAAU,OAAO;AAAA,QAC/B,OAAO,OAAO,SAAS,YAAY;AAAA,QACnC,WAAW,OAAO,qBAAqB;AAAA,MACzC,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,YACJ,SACA,SAC6B;AAC7B,YAAM,QAAQ,qBAAqB,OAAO;AAC1C,YAAM,WAAW,SAAS,qBAAqB;AAC/C,UAAI,MAAM,cAAc,UAAU;AAChC,cAAM,IAAI;AAAA,UACR,6CAA6C,MAAM,SAAS,sBAAiB,QAAQ;AAAA,QAEvF;AAAA,MACF;AACA,aAAO,UAAU,KAAK;AAAA,IACxB;AAAA,EACF;AACF;AAhEgB;","names":[]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* ⋈ 🏃🏻💨 FastNear intents - ESM (@fastnear/intents version 1.6.0) */
|
|
2
|
+
/* https://www.npmjs.com/package/@fastnear/intents/v/1.6.0 */
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
|
+
import {
|
|
6
|
+
ONE_CLICK_BASE_URL
|
|
7
|
+
} from "./types.js";
|
|
8
|
+
class IntentsHttpError extends Error {
|
|
9
|
+
static {
|
|
10
|
+
__name(this, "IntentsHttpError");
|
|
11
|
+
}
|
|
12
|
+
status;
|
|
13
|
+
body;
|
|
14
|
+
constructor(message, status, body) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "IntentsHttpError";
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.body = body;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function parseBody(response) {
|
|
22
|
+
const text = await response.text();
|
|
23
|
+
try {
|
|
24
|
+
return text ? JSON.parse(text) : null;
|
|
25
|
+
} catch {
|
|
26
|
+
return text;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
__name(parseBody, "parseBody");
|
|
30
|
+
function createOneClickClient(options = {}) {
|
|
31
|
+
const baseUrl = (options.baseUrl ?? ONE_CLICK_BASE_URL).replace(/\/$/, "");
|
|
32
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
33
|
+
if (typeof fetchImplementation !== "function") {
|
|
34
|
+
throw new Error("A fetch implementation is required");
|
|
35
|
+
}
|
|
36
|
+
const authHeaders = {};
|
|
37
|
+
if (options.apiKey) authHeaders["X-API-Key"] = options.apiKey;
|
|
38
|
+
if (options.jwt) authHeaders.Authorization = `Bearer ${options.jwt}`;
|
|
39
|
+
async function request(method, path, body) {
|
|
40
|
+
const headers = body === void 0 ? authHeaders : { ...authHeaders, "Content-Type": "application/json" };
|
|
41
|
+
const response = await fetchImplementation(`${baseUrl}${path}`, {
|
|
42
|
+
method,
|
|
43
|
+
headers,
|
|
44
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
45
|
+
});
|
|
46
|
+
const parsed = await parseBody(response);
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const detail = parsed && typeof parsed === "object" && "message" in parsed ? ` \u2014 ${JSON.stringify(parsed.message)}` : "";
|
|
49
|
+
throw new IntentsHttpError(
|
|
50
|
+
`1Click ${method} ${path} failed with ${response.status}${detail}`,
|
|
51
|
+
response.status,
|
|
52
|
+
parsed
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
__name(request, "request");
|
|
58
|
+
return {
|
|
59
|
+
tokens: /* @__PURE__ */ __name(() => request("GET", "/v0/tokens"), "tokens"),
|
|
60
|
+
quote: /* @__PURE__ */ __name((quoteRequest) => request("POST", "/v0/quote", quoteRequest), "quote"),
|
|
61
|
+
status: /* @__PURE__ */ __name(({ depositAddress, depositMemo }) => {
|
|
62
|
+
const query = new URLSearchParams({ depositAddress });
|
|
63
|
+
if (depositMemo) query.set("depositMemo", depositMemo);
|
|
64
|
+
return request("GET", `/v0/status?${query}`);
|
|
65
|
+
}, "status"),
|
|
66
|
+
submitDeposit: /* @__PURE__ */ __name((submitRequest) => request("POST", "/v0/deposit/submit", submitRequest), "submitDeposit"),
|
|
67
|
+
generateIntent: /* @__PURE__ */ __name(({ standard = "nep413", signerId, depositAddress }) => request("POST", "/v0/generate-intent", {
|
|
68
|
+
type: "swap_transfer",
|
|
69
|
+
standard,
|
|
70
|
+
signerId,
|
|
71
|
+
depositAddress
|
|
72
|
+
}), "generateIntent"),
|
|
73
|
+
submitIntent: /* @__PURE__ */ __name(({ signedData }) => request("POST", "/v0/submit-intent", {
|
|
74
|
+
type: "swap_transfer",
|
|
75
|
+
signedData
|
|
76
|
+
}), "submitIntent")
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
__name(createOneClickClient, "createOneClickClient");
|
|
80
|
+
export {
|
|
81
|
+
IntentsHttpError,
|
|
82
|
+
createOneClickClient
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=one-click.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/one-click.ts"],"sourcesContent":["import {\n ONE_CLICK_BASE_URL,\n type OneClickGenerateIntentRequest,\n type OneClickGenerateIntentResponse,\n type OneClickQuoteRequest,\n type OneClickQuoteResponse,\n type OneClickStatusResponse,\n type OneClickSubmitDepositRequest,\n type OneClickSubmitIntentResponse,\n type OneClickToken,\n type SignedIntent,\n} from \"./types.js\";\n\n/** Error thrown for non-2xx 1Click (and solver relay) HTTP responses. */\nexport class IntentsHttpError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"IntentsHttpError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport interface OneClickClientOptions {\n baseUrl?: string;\n /** Partner API key (X-API-Key). Without one, quotes carry a 0.2% platform fee. */\n apiKey?: string;\n /** Legacy JWT bearer token — same fee waiver as apiKey. */\n jwt?: string;\n fetch?: typeof globalThis.fetch;\n}\n\nexport interface OneClickClient {\n /** List supported assets; each entry's assetId is used in quotes. */\n tokens(): Promise<OneClickToken[]>;\n /** Request a quote. dry:true previews pricing; dry:false allocates the depositAddress. */\n quote(request: OneClickQuoteRequest): Promise<OneClickQuoteResponse>;\n /** Poll swap execution by deposit address. */\n status(params: {\n depositAddress: string;\n depositMemo?: string;\n }): Promise<OneClickStatusResponse>;\n /** Optional accelerator: report the deposit tx hash. */\n submitDeposit(\n request: OneClickSubmitDepositRequest,\n ): Promise<OneClickStatusResponse>;\n /** INTENTS deposit type: server builds the swap_transfer intent to sign. */\n generateIntent(\n request: OneClickGenerateIntentRequest,\n ): Promise<OneClickGenerateIntentResponse>;\n /** INTENTS deposit type: submit the signed intent. */\n submitIntent(params: {\n signedData: SignedIntent;\n }): Promise<OneClickSubmitIntentResponse>;\n}\n\nasync function parseBody(response: Response): Promise<unknown> {\n const text = await response.text();\n try {\n return text ? JSON.parse(text) : null;\n } catch {\n return text;\n }\n}\n\n/**\n * Zero-dependency typed client for the hosted 1Click swap API.\n * Works unauthenticated (0.2% platform fee baked into quotes); pass an\n * apiKey from https://partners.near-intents.org/ to waive it.\n */\nexport function createOneClickClient(\n options: OneClickClientOptions = {},\n): OneClickClient {\n const baseUrl = (options.baseUrl ?? ONE_CLICK_BASE_URL).replace(/\\/$/, \"\");\n const fetchImplementation = options.fetch ?? globalThis.fetch;\n if (typeof fetchImplementation !== \"function\") {\n throw new Error(\"A fetch implementation is required\");\n }\n\n const authHeaders: Record<string, string> = {};\n if (options.apiKey) authHeaders[\"X-API-Key\"] = options.apiKey;\n if (options.jwt) authHeaders.Authorization = `Bearer ${options.jwt}`;\n\n async function request<T>(\n method: \"GET\" | \"POST\",\n path: string,\n body?: unknown,\n ): Promise<T> {\n // Content-Type only when a body exists — a bodyless GET with\n // application/json forces an unnecessary CORS preflight in browsers.\n const headers =\n body === undefined\n ? authHeaders\n : { ...authHeaders, \"Content-Type\": \"application/json\" };\n const response = await fetchImplementation(`${baseUrl}${path}`, {\n method,\n headers,\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n });\n const parsed = await parseBody(response);\n if (!response.ok) {\n const detail =\n parsed && typeof parsed === \"object\" && \"message\" in parsed\n ? ` — ${JSON.stringify((parsed as { message: unknown }).message)}`\n : \"\";\n throw new IntentsHttpError(\n `1Click ${method} ${path} failed with ${response.status}${detail}`,\n response.status,\n parsed,\n );\n }\n return parsed as T;\n }\n\n return {\n tokens: () => request(\"GET\", \"/v0/tokens\"),\n quote: (quoteRequest) => request(\"POST\", \"/v0/quote\", quoteRequest),\n status: ({ depositAddress, depositMemo }) => {\n const query = new URLSearchParams({ depositAddress });\n if (depositMemo) query.set(\"depositMemo\", depositMemo);\n return request(\"GET\", `/v0/status?${query}`);\n },\n submitDeposit: (submitRequest) =>\n request(\"POST\", \"/v0/deposit/submit\", submitRequest),\n generateIntent: ({ standard = \"nep413\", signerId, depositAddress }) =>\n request(\"POST\", \"/v0/generate-intent\", {\n type: \"swap_transfer\",\n standard,\n signerId,\n depositAddress,\n }),\n submitIntent: ({ signedData }) =>\n request(\"POST\", \"/v0/submit-intent\", {\n type: \"swap_transfer\",\n signedData,\n }),\n };\n}\n"],"mappings":";;;;AAAA;AAAA,EACE;AAAA,OAUK;AAGA,MAAM,yBAAyB,MAAM;AAAA,EAd5C,OAc4C;AAAA;AAAA;AAAA,EACjC;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,QAAgB,MAAe;AAC1D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAmCA,eAAe,UAAU,UAAsC;AAC7D,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,MAAI;AACF,WAAO,OAAO,KAAK,MAAM,IAAI,IAAI;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAPe;AAcR,SAAS,qBACd,UAAiC,CAAC,GAClB;AAChB,QAAM,WAAW,QAAQ,WAAW,oBAAoB,QAAQ,OAAO,EAAE;AACzE,QAAM,sBAAsB,QAAQ,SAAS,WAAW;AACxD,MAAI,OAAO,wBAAwB,YAAY;AAC7C,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AAEA,QAAM,cAAsC,CAAC;AAC7C,MAAI,QAAQ,OAAQ,aAAY,WAAW,IAAI,QAAQ;AACvD,MAAI,QAAQ,IAAK,aAAY,gBAAgB,UAAU,QAAQ,GAAG;AAElE,iBAAe,QACb,QACA,MACA,MACY;AAGZ,UAAM,UACJ,SAAS,SACL,cACA,EAAE,GAAG,aAAa,gBAAgB,mBAAmB;AAC3D,UAAM,WAAW,MAAM,oBAAoB,GAAG,OAAO,GAAG,IAAI,IAAI;AAAA,MAC9D;AAAA,MACA;AAAA,MACA,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,IAAI,EAAE;AAAA,IAC7D,CAAC;AACD,UAAM,SAAS,MAAM,UAAU,QAAQ;AACvC,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,SACJ,UAAU,OAAO,WAAW,YAAY,aAAa,SACjD,WAAM,KAAK,UAAW,OAAgC,OAAO,CAAC,KAC9D;AACN,YAAM,IAAI;AAAA,QACR,UAAU,MAAM,IAAI,IAAI,gBAAgB,SAAS,MAAM,GAAG,MAAM;AAAA,QAChE,SAAS;AAAA,QACT;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AA7Be;AA+Bf,SAAO;AAAA,IACL,QAAQ,6BAAM,QAAQ,OAAO,YAAY,GAAjC;AAAA,IACR,OAAO,wBAAC,iBAAiB,QAAQ,QAAQ,aAAa,YAAY,GAA3D;AAAA,IACP,QAAQ,wBAAC,EAAE,gBAAgB,YAAY,MAAM;AAC3C,YAAM,QAAQ,IAAI,gBAAgB,EAAE,eAAe,CAAC;AACpD,UAAI,YAAa,OAAM,IAAI,eAAe,WAAW;AACrD,aAAO,QAAQ,OAAO,cAAc,KAAK,EAAE;AAAA,IAC7C,GAJQ;AAAA,IAKR,eAAe,wBAAC,kBACd,QAAQ,QAAQ,sBAAsB,aAAa,GADtC;AAAA,IAEf,gBAAgB,wBAAC,EAAE,WAAW,UAAU,UAAU,eAAe,MAC/D,QAAQ,QAAQ,uBAAuB;AAAA,MACrC,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC,GANa;AAAA,IAOhB,cAAc,wBAAC,EAAE,WAAW,MAC1B,QAAQ,QAAQ,qBAAqB;AAAA,MACnC,MAAM;AAAA,MACN;AAAA,IACF,CAAC,GAJW;AAAA,EAKhB;AACF;AAnEgB;","names":[]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { y as RelayQuoteParams, x as RelayQuote, S as SignedIntent, w as RelayPublishResult, z as RelayStatusResult } from './types-BQpqppU6.js';
|
|
2
|
+
|
|
3
|
+
interface SolverRelayClientOptions {
|
|
4
|
+
url?: string;
|
|
5
|
+
/** Relay API key (X-API-Key header), issued via the partner dashboard. */
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
fetch?: typeof globalThis.fetch;
|
|
8
|
+
}
|
|
9
|
+
interface SolverRelayClient {
|
|
10
|
+
/** Ask connected solvers for quotes (relay-side timeout ~3s). null = no quotes. */
|
|
11
|
+
quote(params: RelayQuoteParams): Promise<RelayQuote[] | null>;
|
|
12
|
+
/** Publish one signed intent against the quote hashes it fills. */
|
|
13
|
+
publishIntent(params: {
|
|
14
|
+
signedData: SignedIntent;
|
|
15
|
+
quoteHashes: string[];
|
|
16
|
+
}): Promise<RelayPublishResult>;
|
|
17
|
+
/** Publish a batch of signed intents. */
|
|
18
|
+
publishIntents(params: {
|
|
19
|
+
signedDatas: SignedIntent[];
|
|
20
|
+
quoteHashes: string[];
|
|
21
|
+
requote?: boolean;
|
|
22
|
+
}): Promise<RelayPublishResult>;
|
|
23
|
+
/** Poll a published intent: PENDING → TX_BROADCASTED → SETTLED. */
|
|
24
|
+
getStatus(params: {
|
|
25
|
+
intentHash: string;
|
|
26
|
+
}): Promise<RelayStatusResult>;
|
|
27
|
+
}
|
|
28
|
+
/** Error carrying the JSON-RPC error object from the solver relay. */
|
|
29
|
+
declare class SolverRelayError extends Error {
|
|
30
|
+
readonly rpcError: unknown;
|
|
31
|
+
constructor(message: string, rpcError: unknown);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Zero-dependency JSON-RPC client for the NEAR Intents solver relay
|
|
35
|
+
* (the message bus that brokers quotes between users and solvers and
|
|
36
|
+
* submits matched intent bundles to intents.near).
|
|
37
|
+
*/
|
|
38
|
+
declare function createSolverRelayClient(options?: SolverRelayClientOptions): SolverRelayClient;
|
|
39
|
+
|
|
40
|
+
export { type SolverRelayClient, type SolverRelayClientOptions, SolverRelayError, createSolverRelayClient };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* ⋈ 🏃🏻💨 FastNear intents - ESM (@fastnear/intents version 1.6.0) */
|
|
2
|
+
/* https://www.npmjs.com/package/@fastnear/intents/v/1.6.0 */
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
|
+
import { IntentsHttpError } from "./one-click.js";
|
|
6
|
+
import {
|
|
7
|
+
SOLVER_RELAY_URL
|
|
8
|
+
} from "./types.js";
|
|
9
|
+
class SolverRelayError extends Error {
|
|
10
|
+
static {
|
|
11
|
+
__name(this, "SolverRelayError");
|
|
12
|
+
}
|
|
13
|
+
rpcError;
|
|
14
|
+
constructor(message, rpcError) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "SolverRelayError";
|
|
17
|
+
this.rpcError = rpcError;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function createSolverRelayClient(options = {}) {
|
|
21
|
+
const url = options.url ?? SOLVER_RELAY_URL;
|
|
22
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
23
|
+
if (typeof fetchImplementation !== "function") {
|
|
24
|
+
throw new Error("A fetch implementation is required");
|
|
25
|
+
}
|
|
26
|
+
const headers = { "Content-Type": "application/json" };
|
|
27
|
+
if (options.apiKey) headers["X-API-Key"] = options.apiKey;
|
|
28
|
+
let nextId = 0;
|
|
29
|
+
async function call(method, params) {
|
|
30
|
+
const id = ++nextId;
|
|
31
|
+
const response = await fetchImplementation(url, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers,
|
|
34
|
+
body: JSON.stringify({ id, jsonrpc: "2.0", method, params: [params] })
|
|
35
|
+
});
|
|
36
|
+
const text = await response.text();
|
|
37
|
+
let parsed = null;
|
|
38
|
+
try {
|
|
39
|
+
parsed = text ? JSON.parse(text) : null;
|
|
40
|
+
} catch {
|
|
41
|
+
parsed = text;
|
|
42
|
+
}
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
throw new IntentsHttpError(
|
|
45
|
+
`Solver relay ${method} failed with ${response.status}`,
|
|
46
|
+
response.status,
|
|
47
|
+
parsed
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const body = parsed;
|
|
51
|
+
if (body && typeof body === "object" && body.error != null) {
|
|
52
|
+
throw new SolverRelayError(
|
|
53
|
+
`Solver relay ${method} returned an error: ${JSON.stringify(body.error)}`,
|
|
54
|
+
body.error
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (!body || typeof body !== "object" || !("result" in body)) {
|
|
58
|
+
throw new SolverRelayError(
|
|
59
|
+
`Solver relay ${method} returned a malformed JSON-RPC envelope`,
|
|
60
|
+
parsed
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return body.result;
|
|
64
|
+
}
|
|
65
|
+
__name(call, "call");
|
|
66
|
+
return {
|
|
67
|
+
quote: /* @__PURE__ */ __name((params) => call("quote", params), "quote"),
|
|
68
|
+
publishIntent: /* @__PURE__ */ __name(({ signedData, quoteHashes }) => call("publish_intent", {
|
|
69
|
+
signed_data: signedData,
|
|
70
|
+
quote_hashes: quoteHashes
|
|
71
|
+
}), "publishIntent"),
|
|
72
|
+
publishIntents: /* @__PURE__ */ __name(({ signedDatas, quoteHashes, requote }) => call("publish_intents", {
|
|
73
|
+
signed_datas: signedDatas,
|
|
74
|
+
quote_hashes: quoteHashes,
|
|
75
|
+
...requote === void 0 ? {} : { requote }
|
|
76
|
+
}), "publishIntents"),
|
|
77
|
+
getStatus: /* @__PURE__ */ __name(({ intentHash }) => call("get_status", { intent_hash: intentHash }), "getStatus")
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
__name(createSolverRelayClient, "createSolverRelayClient");
|
|
81
|
+
export {
|
|
82
|
+
SolverRelayError,
|
|
83
|
+
createSolverRelayClient
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=relay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/relay.ts"],"sourcesContent":["import { IntentsHttpError } from \"./one-click.js\";\nimport {\n SOLVER_RELAY_URL,\n type RelayPublishResult,\n type RelayQuote,\n type RelayQuoteParams,\n type RelayStatusResult,\n type SignedIntent,\n} from \"./types.js\";\n\nexport interface SolverRelayClientOptions {\n url?: string;\n /** Relay API key (X-API-Key header), issued via the partner dashboard. */\n apiKey?: string;\n fetch?: typeof globalThis.fetch;\n}\n\nexport interface SolverRelayClient {\n /** Ask connected solvers for quotes (relay-side timeout ~3s). null = no quotes. */\n quote(params: RelayQuoteParams): Promise<RelayQuote[] | null>;\n /** Publish one signed intent against the quote hashes it fills. */\n publishIntent(params: {\n signedData: SignedIntent;\n quoteHashes: string[];\n }): Promise<RelayPublishResult>;\n /** Publish a batch of signed intents. */\n publishIntents(params: {\n signedDatas: SignedIntent[];\n quoteHashes: string[];\n requote?: boolean;\n }): Promise<RelayPublishResult>;\n /** Poll a published intent: PENDING → TX_BROADCASTED → SETTLED. */\n getStatus(params: { intentHash: string }): Promise<RelayStatusResult>;\n}\n\n/** Error carrying the JSON-RPC error object from the solver relay. */\nexport class SolverRelayError extends Error {\n readonly rpcError: unknown;\n\n constructor(message: string, rpcError: unknown) {\n super(message);\n this.name = \"SolverRelayError\";\n this.rpcError = rpcError;\n }\n}\n\n/**\n * Zero-dependency JSON-RPC client for the NEAR Intents solver relay\n * (the message bus that brokers quotes between users and solvers and\n * submits matched intent bundles to intents.near).\n */\nexport function createSolverRelayClient(\n options: SolverRelayClientOptions = {},\n): SolverRelayClient {\n const url = options.url ?? SOLVER_RELAY_URL;\n const fetchImplementation = options.fetch ?? globalThis.fetch;\n if (typeof fetchImplementation !== \"function\") {\n throw new Error(\"A fetch implementation is required\");\n }\n\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (options.apiKey) headers[\"X-API-Key\"] = options.apiKey;\n\n let nextId = 0;\n\n async function call<T>(method: string, params: unknown): Promise<T> {\n const id = ++nextId;\n const response = await fetchImplementation(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ id, jsonrpc: \"2.0\", method, params: [params] }),\n });\n const text = await response.text();\n let parsed: unknown = null;\n try {\n parsed = text ? JSON.parse(text) : null;\n } catch {\n parsed = text;\n }\n if (!response.ok) {\n throw new IntentsHttpError(\n `Solver relay ${method} failed with ${response.status}`,\n response.status,\n parsed,\n );\n }\n const body = parsed as { result?: T; error?: unknown };\n if (body && typeof body === \"object\" && body.error != null) {\n throw new SolverRelayError(\n `Solver relay ${method} returned an error: ${JSON.stringify(body.error)}`,\n body.error,\n );\n }\n if (!body || typeof body !== \"object\" || !(\"result\" in body)) {\n throw new SolverRelayError(\n `Solver relay ${method} returned a malformed JSON-RPC envelope`,\n parsed,\n );\n }\n return body.result as T;\n }\n\n return {\n quote: (params) => call(\"quote\", params),\n publishIntent: ({ signedData, quoteHashes }) =>\n call(\"publish_intent\", {\n signed_data: signedData,\n quote_hashes: quoteHashes,\n }),\n publishIntents: ({ signedDatas, quoteHashes, requote }) =>\n call(\"publish_intents\", {\n signed_datas: signedDatas,\n quote_hashes: quoteHashes,\n ...(requote === undefined ? {} : { requote }),\n }),\n getStatus: ({ intentHash }) =>\n call(\"get_status\", { intent_hash: intentHash }),\n };\n}\n"],"mappings":";;;;AAAA,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,OAMK;AA4BA,MAAM,yBAAyB,MAAM;AAAA,EApC5C,OAoC4C;AAAA;AAAA;AAAA,EACjC;AAAA,EAET,YAAY,SAAiB,UAAmB;AAC9C,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW;AAAA,EAClB;AACF;AAOO,SAAS,wBACd,UAAoC,CAAC,GAClB;AACnB,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,sBAAsB,QAAQ,SAAS,WAAW;AACxD,MAAI,OAAO,wBAAwB,YAAY;AAC7C,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AAEA,QAAM,UAAkC,EAAE,gBAAgB,mBAAmB;AAC7E,MAAI,QAAQ,OAAQ,SAAQ,WAAW,IAAI,QAAQ;AAEnD,MAAI,SAAS;AAEb,iBAAe,KAAQ,QAAgB,QAA6B;AAClE,UAAM,KAAK,EAAE;AACb,UAAM,WAAW,MAAM,oBAAoB,KAAK;AAAA,MAC9C,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,IAAI,SAAS,OAAO,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC;AAAA,IACvE,CAAC;AACD,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,SAAkB;AACtB,QAAI;AACF,eAAS,OAAO,KAAK,MAAM,IAAI,IAAI;AAAA,IACrC,QAAQ;AACN,eAAS;AAAA,IACX;AACA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,gBAAgB,MAAM,gBAAgB,SAAS,MAAM;AAAA,QACrD,SAAS;AAAA,QACT;AAAA,MACF;AAAA,IACF;AACA,UAAM,OAAO;AACb,QAAI,QAAQ,OAAO,SAAS,YAAY,KAAK,SAAS,MAAM;AAC1D,YAAM,IAAI;AAAA,QACR,gBAAgB,MAAM,uBAAuB,KAAK,UAAU,KAAK,KAAK,CAAC;AAAA,QACvE,KAAK;AAAA,MACP;AAAA,IACF;AACA,QAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,EAAE,YAAY,OAAO;AAC5D,YAAM,IAAI;AAAA,QACR,gBAAgB,MAAM;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AAnCe;AAqCf,SAAO;AAAA,IACL,OAAO,wBAAC,WAAW,KAAK,SAAS,MAAM,GAAhC;AAAA,IACP,eAAe,wBAAC,EAAE,YAAY,YAAY,MACxC,KAAK,kBAAkB;AAAA,MACrB,aAAa;AAAA,MACb,cAAc;AAAA,IAChB,CAAC,GAJY;AAAA,IAKf,gBAAgB,wBAAC,EAAE,aAAa,aAAa,QAAQ,MACnD,KAAK,mBAAmB;AAAA,MACtB,cAAc;AAAA,MACd,cAAc;AAAA,MACd,GAAI,YAAY,SAAY,CAAC,IAAI,EAAE,QAAQ;AAAA,IAC7C,CAAC,GALa;AAAA,IAMhB,WAAW,wBAAC,EAAE,WAAW,MACvB,KAAK,cAAc,EAAE,aAAa,WAAW,CAAC,GADrC;AAAA,EAEb;AACF;AAnEgB;","names":[]}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/* ⋈ 🏃🏻💨 FastNear intents - ESM (@fastnear/intents version 1.6.0) */
|
|
2
|
+
/* https://www.npmjs.com/package/@fastnear/intents/v/1.6.0 */
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
|
+
import { base64ToBytes, bytesToBase64, toBase58 } from "@fastnear/utils";
|
|
6
|
+
import {
|
|
7
|
+
INTENTS_CONTRACT_ID
|
|
8
|
+
} from "./types.js";
|
|
9
|
+
function randomNonce() {
|
|
10
|
+
return crypto.getRandomValues(new Uint8Array(32));
|
|
11
|
+
}
|
|
12
|
+
__name(randomNonce, "randomNonce");
|
|
13
|
+
const DEFAULT_DEADLINE_MS = 5 * 60 * 1e3;
|
|
14
|
+
function defaultDeadline(ms = DEFAULT_DEADLINE_MS) {
|
|
15
|
+
return new Date(Date.now() + ms).toISOString();
|
|
16
|
+
}
|
|
17
|
+
__name(defaultDeadline, "defaultDeadline");
|
|
18
|
+
function buildIntentMessage({
|
|
19
|
+
signerId,
|
|
20
|
+
intents,
|
|
21
|
+
deadline
|
|
22
|
+
}) {
|
|
23
|
+
if (!signerId) throw new Error("signerId is required");
|
|
24
|
+
if (!Array.isArray(intents) || intents.length === 0) {
|
|
25
|
+
throw new Error("At least one intent is required");
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
signer_id: signerId,
|
|
29
|
+
deadline: deadline ?? defaultDeadline(),
|
|
30
|
+
intents
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
__name(buildIntentMessage, "buildIntentMessage");
|
|
34
|
+
function encodeIntentSignature(signature) {
|
|
35
|
+
if (typeof signature === "string") {
|
|
36
|
+
if (signature.startsWith("ed25519:") || signature.startsWith("secp256k1:")) {
|
|
37
|
+
return signature;
|
|
38
|
+
}
|
|
39
|
+
return encodeIntentSignature(base64ToBytes(signature));
|
|
40
|
+
}
|
|
41
|
+
if (signature.length === 64) return `ed25519:${toBase58(signature)}`;
|
|
42
|
+
if (signature.length === 65) return `secp256k1:${toBase58(signature)}`;
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Unsupported NEP-413 signature length: ${signature.length} (expected 64 or 65 bytes)`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
__name(encodeIntentSignature, "encodeIntentSignature");
|
|
48
|
+
function normalizeIntentPublicKey(publicKey) {
|
|
49
|
+
if (!publicKey) throw new Error("A public key is required");
|
|
50
|
+
return publicKey.includes(":") ? publicKey : `ed25519:${publicKey}`;
|
|
51
|
+
}
|
|
52
|
+
__name(normalizeIntentPublicKey, "normalizeIntentPublicKey");
|
|
53
|
+
function toSignedIntent({
|
|
54
|
+
message,
|
|
55
|
+
nonce,
|
|
56
|
+
recipient = INTENTS_CONTRACT_ID,
|
|
57
|
+
publicKey,
|
|
58
|
+
signature,
|
|
59
|
+
callbackUrl
|
|
60
|
+
}) {
|
|
61
|
+
if (nonce.length !== 32) {
|
|
62
|
+
throw new Error(`NEP-413 nonce must be exactly 32 bytes, got ${nonce.length}`);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
standard: "nep413",
|
|
66
|
+
payload: {
|
|
67
|
+
message: typeof message === "string" ? message : JSON.stringify(message),
|
|
68
|
+
nonce: bytesToBase64(nonce),
|
|
69
|
+
recipient,
|
|
70
|
+
...callbackUrl ? { callbackUrl } : {}
|
|
71
|
+
},
|
|
72
|
+
public_key: normalizeIntentPublicKey(publicKey),
|
|
73
|
+
signature: encodeIntentSignature(signature)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
__name(toSignedIntent, "toSignedIntent");
|
|
77
|
+
function unsignedPayloadParts(input) {
|
|
78
|
+
const payload = "payload" in input && typeof input.payload === "object" ? input.payload : input;
|
|
79
|
+
if ("standard" in input && typeof input.standard === "string" && input.standard !== "nep413") {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`This signer only signs nep413 payloads; got standard "${input.standard}"`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (typeof payload?.message !== "string" || typeof payload?.recipient !== "string") {
|
|
85
|
+
throw new Error("Unsigned payload must carry message and recipient strings");
|
|
86
|
+
}
|
|
87
|
+
const nonce = typeof payload.nonce === "string" ? base64ToBytes(payload.nonce) : payload.nonce;
|
|
88
|
+
if (!(nonce instanceof Uint8Array) || nonce.length !== 32) {
|
|
89
|
+
throw new Error("Unsigned payload nonce must decode to exactly 32 bytes");
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
message: payload.message,
|
|
93
|
+
nonce,
|
|
94
|
+
recipient: payload.recipient,
|
|
95
|
+
...payload.callbackUrl ? { callbackUrl: payload.callbackUrl } : {}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
__name(unsignedPayloadParts, "unsignedPayloadParts");
|
|
99
|
+
function assertExpectedRecipient(recipient, options) {
|
|
100
|
+
const expected = options?.expectedRecipient ?? INTENTS_CONTRACT_ID;
|
|
101
|
+
if (recipient !== expected) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Refusing to sign a payload for recipient "${recipient}" \u2014 expected "${expected}". Pass { expectedRecipient } only when intentionally targeting a different verifier.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
__name(assertExpectedRecipient, "assertExpectedRecipient");
|
|
108
|
+
function signerIdFromMessage(message) {
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(message);
|
|
111
|
+
return typeof parsed?.signer_id === "string" ? parsed.signer_id : void 0;
|
|
112
|
+
} catch {
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
__name(signerIdFromMessage, "signerIdFromMessage");
|
|
117
|
+
function createWalletIntentSigner({
|
|
118
|
+
wallet,
|
|
119
|
+
network = "mainnet"
|
|
120
|
+
}) {
|
|
121
|
+
if (!wallet || typeof wallet.signMessage !== "function") {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"A FastNEAR wallet module with signMessage is required (connect @fastnear/wallet first)"
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
async function signParts({
|
|
127
|
+
message,
|
|
128
|
+
nonce,
|
|
129
|
+
recipient,
|
|
130
|
+
callbackUrl,
|
|
131
|
+
expectedSigner
|
|
132
|
+
}) {
|
|
133
|
+
if (callbackUrl) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
"Wallet intent signing cannot bind callbackUrl payloads; use the local signer or drop callbackUrl"
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
const signed = await wallet.signMessage({
|
|
139
|
+
message,
|
|
140
|
+
recipient,
|
|
141
|
+
nonce,
|
|
142
|
+
network
|
|
143
|
+
});
|
|
144
|
+
if (expectedSigner && signed.accountId && signed.accountId !== expectedSigner) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Wallet signed as ${signed.accountId} but the intent names ${expectedSigner}; reconnect the intended account`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return toSignedIntent({
|
|
150
|
+
message,
|
|
151
|
+
nonce,
|
|
152
|
+
recipient,
|
|
153
|
+
publicKey: signed.publicKey,
|
|
154
|
+
signature: signed.signature,
|
|
155
|
+
callbackUrl
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
__name(signParts, "signParts");
|
|
159
|
+
return {
|
|
160
|
+
async signIntents(params) {
|
|
161
|
+
const signerId = params.signerId ?? (typeof wallet.accountId === "function" ? wallet.accountId({ network }) : null);
|
|
162
|
+
if (!signerId) {
|
|
163
|
+
throw new Error(`No wallet account connected on ${network}`);
|
|
164
|
+
}
|
|
165
|
+
const message = buildIntentMessage({
|
|
166
|
+
signerId,
|
|
167
|
+
intents: params.intents,
|
|
168
|
+
deadline: params.deadline
|
|
169
|
+
});
|
|
170
|
+
return signParts({
|
|
171
|
+
message: JSON.stringify(message),
|
|
172
|
+
nonce: params.nonce ?? randomNonce(),
|
|
173
|
+
recipient: params.verifyingContract ?? INTENTS_CONTRACT_ID,
|
|
174
|
+
expectedSigner: signerId
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
async signPayload(payload, options) {
|
|
178
|
+
const parts = unsignedPayloadParts(payload);
|
|
179
|
+
assertExpectedRecipient(parts.recipient, options);
|
|
180
|
+
return signParts({
|
|
181
|
+
...parts,
|
|
182
|
+
expectedSigner: signerIdFromMessage(parts.message)
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
__name(createWalletIntentSigner, "createWalletIntentSigner");
|
|
188
|
+
export {
|
|
189
|
+
buildIntentMessage,
|
|
190
|
+
createWalletIntentSigner,
|
|
191
|
+
defaultDeadline,
|
|
192
|
+
encodeIntentSignature,
|
|
193
|
+
normalizeIntentPublicKey,
|
|
194
|
+
randomNonce,
|
|
195
|
+
toSignedIntent,
|
|
196
|
+
unsignedPayloadParts
|
|
197
|
+
};
|
|
198
|
+
//# sourceMappingURL=signing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/signing.ts"],"sourcesContent":["import { base64ToBytes, bytesToBase64, toBase58 } from \"@fastnear/utils\";\nimport {\n INTENTS_CONTRACT_ID,\n type Intent,\n type IntentMessage,\n type IntentSigner,\n type SignIntentsParams,\n type GeneratedUnsignedIntent,\n type SignPayloadOptions,\n type SignedIntentNep413,\n type UnsignedNep413Payload,\n} from \"./types.js\";\n\n/** Crypto-random 32-byte NEP-413 nonce (the verifier's replay nonce). */\nexport function randomNonce(): Uint8Array {\n return crypto.getRandomValues(new Uint8Array(32));\n}\n\nconst DEFAULT_DEADLINE_MS = 5 * 60 * 1000;\n\n/** ISO-8601 deadline `ms` milliseconds from now (default 5 minutes). */\nexport function defaultDeadline(ms: number = DEFAULT_DEADLINE_MS): string {\n return new Date(Date.now() + ms).toISOString();\n}\n\n/** Build the inner intent message that gets signed. */\nexport function buildIntentMessage({\n signerId,\n intents,\n deadline,\n}: {\n signerId: string;\n intents: Intent[];\n deadline?: string;\n}): IntentMessage {\n if (!signerId) throw new Error(\"signerId is required\");\n if (!Array.isArray(intents) || intents.length === 0) {\n throw new Error(\"At least one intent is required\");\n }\n return {\n signer_id: signerId,\n deadline: deadline ?? defaultDeadline(),\n intents,\n };\n}\n\n/**\n * Encode a NEP-413 signature the way intents.near expects it:\n * `ed25519:<base58>` (or `secp256k1:<base58>` for 65-byte signatures).\n *\n * NEAR wallets return the signature as plain base64 per NEP-413 — passing\n * that through unconverted is the most common integration bug.\n */\nexport function encodeIntentSignature(\n signature: string | Uint8Array,\n): string {\n if (typeof signature === \"string\") {\n if (\n signature.startsWith(\"ed25519:\") ||\n signature.startsWith(\"secp256k1:\")\n ) {\n return signature;\n }\n return encodeIntentSignature(base64ToBytes(signature));\n }\n if (signature.length === 64) return `ed25519:${toBase58(signature)}`;\n if (signature.length === 65) return `secp256k1:${toBase58(signature)}`;\n throw new Error(\n `Unsupported NEP-413 signature length: ${signature.length} (expected 64 or 65 bytes)`,\n );\n}\n\n/** Ensure a public key string carries its curve prefix. */\nexport function normalizeIntentPublicKey(publicKey: string): string {\n if (!publicKey) throw new Error(\"A public key is required\");\n return publicKey.includes(\":\") ? publicKey : `ed25519:${publicKey}`;\n}\n\n/**\n * Assemble the signed MultiPayload from NEP-413 parts. Accepts the\n * signature as wallet base64, raw bytes, or an already-prefixed string.\n */\nexport function toSignedIntent({\n message,\n nonce,\n recipient = INTENTS_CONTRACT_ID,\n publicKey,\n signature,\n callbackUrl,\n}: {\n message: IntentMessage | string;\n nonce: Uint8Array;\n recipient?: string;\n publicKey: string;\n signature: string | Uint8Array;\n callbackUrl?: string;\n}): SignedIntentNep413 {\n if (nonce.length !== 32) {\n throw new Error(`NEP-413 nonce must be exactly 32 bytes, got ${nonce.length}`);\n }\n return {\n standard: \"nep413\",\n payload: {\n message: typeof message === \"string\" ? message : JSON.stringify(message),\n nonce: bytesToBase64(nonce),\n recipient,\n ...(callbackUrl ? { callbackUrl } : {}),\n },\n public_key: normalizeIntentPublicKey(publicKey),\n signature: encodeIntentSignature(signature),\n };\n}\n\n/**\n * Normalize an unsigned payload as 1Click's generate-intent returns it.\n * Accepts either the bare payload or the `{ standard, payload }` wrapper,\n * and decodes a base64 nonce string to bytes.\n */\nexport function unsignedPayloadParts(\n input: GeneratedUnsignedIntent,\n): { message: string; nonce: Uint8Array; recipient: string; callbackUrl?: string } {\n const payload =\n \"payload\" in input && typeof input.payload === \"object\"\n ? input.payload\n : (input as UnsignedNep413Payload);\n if (\n \"standard\" in input &&\n typeof input.standard === \"string\" &&\n input.standard !== \"nep413\"\n ) {\n throw new Error(\n `This signer only signs nep413 payloads; got standard \"${input.standard}\"`,\n );\n }\n if (typeof payload?.message !== \"string\" || typeof payload?.recipient !== \"string\") {\n throw new Error(\"Unsigned payload must carry message and recipient strings\");\n }\n const nonce =\n typeof payload.nonce === \"string\"\n ? base64ToBytes(payload.nonce)\n : payload.nonce;\n if (!(nonce instanceof Uint8Array) || nonce.length !== 32) {\n throw new Error(\"Unsigned payload nonce must decode to exactly 32 bytes\");\n }\n return {\n message: payload.message,\n nonce,\n recipient: payload.recipient,\n ...(payload.callbackUrl ? { callbackUrl: payload.callbackUrl } : {}),\n };\n}\n\nfunction assertExpectedRecipient(\n recipient: string,\n options?: SignPayloadOptions,\n): void {\n const expected = options?.expectedRecipient ?? INTENTS_CONTRACT_ID;\n if (recipient !== expected) {\n throw new Error(\n `Refusing to sign a payload for recipient \"${recipient}\" — expected \"${expected}\". ` +\n \"Pass { expectedRecipient } only when intentionally targeting a different verifier.\",\n );\n }\n}\n\n/** Best-effort signer_id extraction from an intent-message JSON string. */\nfunction signerIdFromMessage(message: string): string | undefined {\n try {\n const parsed = JSON.parse(message);\n return typeof parsed?.signer_id === \"string\" ? parsed.signer_id : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The @fastnear/wallet surface this package needs — structural, so any\n * object with a NEP-413 `signMessage` (and optionally `accountId`) works.\n */\nexport interface FastNearWalletLike {\n accountId?(options?: { network?: \"mainnet\" | \"testnet\" }): string | null;\n signMessage(params: {\n message: string;\n recipient: string;\n nonce: Uint8Array;\n network?: \"mainnet\" | \"testnet\";\n }): Promise<{ accountId: string; publicKey: string; signature: string }>;\n}\n\nexport interface WalletIntentSignerOptions {\n wallet: FastNearWalletLike;\n /** NEAR Intents is mainnet-oriented; override only for test deployments. */\n network?: \"mainnet\" | \"testnet\";\n}\n\n/**\n * Sign intent messages with a connected FastNEAR wallet via NEP-413.\n *\n * NEP-413 requires a full-access key, so wallets sign with the account's\n * own key — FunctionCall-access session keys cannot authorize intents.\n * The wallet's base64 signature is re-encoded to `ed25519:<base58>`.\n */\nexport function createWalletIntentSigner({\n wallet,\n network = \"mainnet\",\n}: WalletIntentSignerOptions): IntentSigner {\n if (!wallet || typeof wallet.signMessage !== \"function\") {\n throw new Error(\n \"A FastNEAR wallet module with signMessage is required (connect @fastnear/wallet first)\",\n );\n }\n\n async function signParts({\n message,\n nonce,\n recipient,\n callbackUrl,\n expectedSigner,\n }: {\n message: string;\n nonce: Uint8Array;\n recipient: string;\n callbackUrl?: string;\n expectedSigner?: string;\n }): Promise<SignedIntentNep413> {\n if (callbackUrl) {\n // NEP-413 binds callbackUrl into the signed digest, but the wallet\n // signMessage transport has no callbackUrl parameter — signing would\n // produce a payload that advertises a callbackUrl the signature does\n // not cover, which the verifier rejects.\n throw new Error(\n \"Wallet intent signing cannot bind callbackUrl payloads; use the local signer or drop callbackUrl\",\n );\n }\n const signed = await wallet.signMessage({\n message,\n recipient,\n nonce,\n network,\n });\n\n if (expectedSigner && signed.accountId && signed.accountId !== expectedSigner) {\n throw new Error(\n `Wallet signed as ${signed.accountId} but the intent names ${expectedSigner}; reconnect the intended account`,\n );\n }\n\n return toSignedIntent({\n message,\n nonce,\n recipient,\n publicKey: signed.publicKey,\n signature: signed.signature,\n callbackUrl,\n });\n }\n\n return {\n async signIntents(params: SignIntentsParams): Promise<SignedIntentNep413> {\n const signerId =\n params.signerId ??\n (typeof wallet.accountId === \"function\"\n ? wallet.accountId({ network })\n : null);\n if (!signerId) {\n throw new Error(`No wallet account connected on ${network}`);\n }\n\n const message = buildIntentMessage({\n signerId,\n intents: params.intents,\n deadline: params.deadline,\n });\n return signParts({\n message: JSON.stringify(message),\n nonce: params.nonce ?? randomNonce(),\n recipient: params.verifyingContract ?? INTENTS_CONTRACT_ID,\n expectedSigner: signerId,\n });\n },\n\n async signPayload(\n payload: GeneratedUnsignedIntent,\n options?: SignPayloadOptions,\n ): Promise<SignedIntentNep413> {\n const parts = unsignedPayloadParts(payload);\n assertExpectedRecipient(parts.recipient, options);\n return signParts({\n ...parts,\n expectedSigner: signerIdFromMessage(parts.message),\n });\n },\n };\n}\n"],"mappings":"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","names":[]}
|