@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 ADDED
@@ -0,0 +1,249 @@
1
+ # `@fastnear/intents`
2
+
3
+ NEAR Intents (the multichain intent protocol settled by the
4
+ [`intents.near`](https://docs.near-intents.org/integration/verifier-contract/introduction.md)
5
+ verifier) for end users and AI agents: a zero-dependency typed client for the
6
+ hosted [1Click swap API](https://docs.near-intents.org/integration/distribution-channels/1click-api),
7
+ NEP-413 intent signing with either a connected FastNEAR wallet or a raw
8
+ server-side key, deposit/withdraw/balance helpers for the verifier itself, and
9
+ a JSON-RPC client for the solver relay.
10
+
11
+ Ships as ESM + CJS + a browser IIFE global (`nearIntents`). The browser entry
12
+ never touches private keys — local-key signing lives in
13
+ `@fastnear/intents/node`.
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ npm install @fastnear/intents
19
+ ```
20
+
21
+ Browser (IIFE globals):
22
+
23
+ ```html
24
+ <script src="https://js.fastnear.com/wallet.js"></script>
25
+ <script src="https://js.fastnear.com/intents.js"></script>
26
+ ```
27
+
28
+ ## Quote and track a swap (1Click)
29
+
30
+ Works unauthenticated out of the box — quotes then carry a 0.2% platform fee.
31
+ An API key from [partners.near-intents.org](https://partners.near-intents.org/)
32
+ waives it.
33
+
34
+ ```js
35
+ import { createOneClickClient } from "@fastnear/intents";
36
+
37
+ const oneClick = createOneClickClient({ apiKey: process.env.ONE_CLICK_API_KEY });
38
+
39
+ // 1. Discover assets — each entry's assetId is used everywhere else.
40
+ const tokens = await oneClick.tokens();
41
+
42
+ // 2. Preview pricing with dry:true (free, no commitment), then commit with
43
+ // dry:false to receive the depositAddress that drives the swap.
44
+ const quote = await oneClick.quote({
45
+ dry: false,
46
+ swapType: "EXACT_INPUT",
47
+ slippageTolerance: 100, // bps
48
+ originAsset: "nep141:wrap.near",
49
+ destinationAsset: "nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near",
50
+ amount: "1000000000000000000000000",
51
+ depositType: "ORIGIN_CHAIN",
52
+ refundTo: "alice.near",
53
+ refundType: "ORIGIN_CHAIN",
54
+ recipient: "0x2527D02599Ba641c19FEa793cD0F167589a0f10D",
55
+ recipientType: "DESTINATION_CHAIN",
56
+ deadline: new Date(Date.now() + 10 * 60_000).toISOString(),
57
+ });
58
+
59
+ // 3. Send the input tokens to quote.quote.depositAddress (see deposits below),
60
+ // then poll until SUCCESS | REFUNDED | FAILED.
61
+ const status = await oneClick.status({
62
+ depositAddress: quote.quote.depositAddress,
63
+ });
64
+ ```
65
+
66
+ ## Sign intents with a wallet (browser)
67
+
68
+ NEP-413 requires a **full-access key**, so wallets sign with the account's own
69
+ key — FunctionCall-access session keys cannot authorize intents. Wallets
70
+ return the signature as base64; the signer re-encodes it to the
71
+ `ed25519:<base58>` form the verifier expects.
72
+
73
+ ```js
74
+ import { createWalletIntentSigner } from "@fastnear/intents";
75
+ // window.nearWallet from https://js.fastnear.com/wallet.js, already connected.
76
+
77
+ const signer = createWalletIntentSigner({ wallet: nearWallet });
78
+
79
+ const signed = await signer.signIntents({
80
+ intents: [
81
+ {
82
+ intent: "token_diff",
83
+ diff: {
84
+ "nep141:usdc.near": "-1000000",
85
+ "nep141:usdt.near": "1000000",
86
+ },
87
+ },
88
+ ],
89
+ });
90
+ // -> { standard: "nep413", payload: {...}, public_key, signature } — ready for
91
+ // oneClick.submitIntent, relay.publishIntent, or intents.near execute_intents.
92
+ ```
93
+
94
+ ## Sign intents with a raw key (Node / agents)
95
+
96
+ ```js
97
+ import { createOneClickClient } from "@fastnear/intents";
98
+ import { createLocalIntentSigner } from "@fastnear/intents/node";
99
+
100
+ const signer = createLocalIntentSigner({
101
+ accountId: process.env.NEAR_ACCOUNT_ID,
102
+ privateKey: process.env.NEAR_PRIVATE_KEY, // full-access, server-side only
103
+ });
104
+
105
+ // INTENTS deposit type: balances already inside intents.near swap without a
106
+ // new on-chain transaction — 1Click builds the payload, you sign and submit.
107
+ const oneClick = createOneClickClient();
108
+ const quote = await oneClick.quote({
109
+ dry: false,
110
+ swapType: "EXACT_INPUT",
111
+ slippageTolerance: 100,
112
+ originAsset: "nep141:wrap.near",
113
+ destinationAsset: "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
114
+ amount: "1000000000000000000000000",
115
+ depositType: "INTENTS", // input funds already sit inside intents.near
116
+ refundTo: process.env.NEAR_ACCOUNT_ID,
117
+ refundType: "INTENTS",
118
+ recipient: process.env.NEAR_ACCOUNT_ID,
119
+ recipientType: "INTENTS",
120
+ deadline: new Date(Date.now() + 10 * 60_000).toISOString(),
121
+ });
122
+ const { intent } = await oneClick.generateIntent({
123
+ signerId: process.env.NEAR_ACCOUNT_ID,
124
+ depositAddress: quote.quote.depositAddress,
125
+ });
126
+ // The server chose the message, nonce, and recipient. signPayload pins the
127
+ // recipient to intents.near and signs the payload verbatim.
128
+ const signed = await signer.signPayload(intent);
129
+ const { intentHash } = await oneClick.submitIntent({ signedData: signed });
130
+ ```
131
+
132
+ ## Deposits, balances, and withdrawals (verifier)
133
+
134
+ Helpers return plain FastNEAR action shapes — pass them to `near.sendTx` or
135
+ `nearWallet.sendTransaction`. Native NEAR must be wrapped first; balances
136
+ inside the verifier are NEP-245 multi-token entries keyed like
137
+ `nep141:<contract>`.
138
+
139
+ ```js
140
+ import {
141
+ ftDepositAction,
142
+ wrapNearAction,
143
+ ftWithdrawAction,
144
+ mtBatchBalances,
145
+ } from "@fastnear/intents";
146
+
147
+ // Wrap native NEAR, then deposit the wNEAR into intents.near.
148
+ await near.sendTx({
149
+ receiverId: "wrap.near",
150
+ actions: [wrapNearAction({ amountYocto: "1000000000000000000000000" })],
151
+ });
152
+ await near.sendTx({
153
+ receiverId: "wrap.near",
154
+ actions: [ftDepositAction({ amount: "1000000000000000000000000" })],
155
+ });
156
+
157
+ // Internal balances (injected view keeps this package api-free).
158
+ const balances = await mtBatchBalances({
159
+ accountId: "alice.near",
160
+ tokenIds: ["nep141:wrap.near", "nep141:usdc.near"],
161
+ view: near.view,
162
+ });
163
+
164
+ // Withdraw back out (plain token id, no nep141: prefix; refundable when msg
165
+ // is omitted).
166
+ await near.sendTx({
167
+ receiverId: "intents.near",
168
+ actions: [
169
+ ftWithdrawAction({
170
+ token: "wrap.near",
171
+ receiverId: "alice.near",
172
+ amount: balances["nep141:wrap.near"],
173
+ }),
174
+ ],
175
+ });
176
+ ```
177
+
178
+ ## Solver relay (direct flows)
179
+
180
+ The relay brokers quotes between users and solvers and submits matched intent
181
+ bundles to the verifier. It is the lower-level alternative to 1Click; relay
182
+ API keys come from the partner dashboard.
183
+
184
+ ```js
185
+ import { createSolverRelayClient } from "@fastnear/intents/relay";
186
+
187
+ const relay = createSolverRelayClient({ apiKey: process.env.RELAY_API_KEY });
188
+
189
+ const quotes = await relay.quote({
190
+ defuse_asset_identifier_in: "nep141:usdc.near",
191
+ defuse_asset_identifier_out: "nep141:usdt.near",
192
+ exact_amount_in: "1000000",
193
+ });
194
+
195
+ const best = quotes?.[0];
196
+ const signed = await signer.signIntents({
197
+ intents: [
198
+ {
199
+ intent: "token_diff",
200
+ diff: {
201
+ "nep141:usdc.near": `-${best.amount_in}`,
202
+ "nep141:usdt.near": best.amount_out,
203
+ },
204
+ },
205
+ ],
206
+ deadline: best.expiration_time,
207
+ });
208
+
209
+ const { intent_hash } = await relay.publishIntent({
210
+ signedData: signed,
211
+ quoteHashes: [best.quote_hash],
212
+ });
213
+ // Poll: PENDING -> TX_BROADCASTED -> SETTLED
214
+ const status = await relay.getStatus({ intentHash: intent_hash });
215
+ ```
216
+
217
+ ## Protocol constraints
218
+
219
+ - Only `intents.near` on NEAR mainnet; there is no meaningful public testnet
220
+ deployment of the intents stack.
221
+ - NEP-413 signatures require a full-access key. The verifier additionally
222
+ checks the key is authorized for the `signer_id` (registered with
223
+ `add_public_key`, or matching an implicit account).
224
+ - Signature encoding is `ed25519:<base58>` — NOT the base64 wallets return.
225
+ The signers in this package own that conversion.
226
+ - Native NEAR is not a verifier asset: wrap to wNEAR on deposit; the
227
+ `native_withdraw` intent is the only native exit.
228
+ - Amounts are base-unit strings everywhere; `token_diff` diffs must net to
229
+ zero per token across the executed batch (a solver signs the mirror diff).
230
+ - Unauthenticated 1Click use adds a 0.2% platform fee to quotes.
231
+
232
+ ## Entrypoints
233
+
234
+ - `@fastnear/intents` — types, 1Click client, wallet signer, verifier
235
+ helpers, solver relay re-export (browser-safe; global `nearIntents`)
236
+ - `@fastnear/intents/relay` — solver relay JSON-RPC client
237
+ - `@fastnear/intents/node` — local full-access-key signer (server-side only)
238
+
239
+ ## Release gate
240
+
241
+ Run the [mainnet QA runbook](./MAINNET_QA.md) before releasing: the free
242
+ dry smoke (`node scripts/smoke-intents-dry.mjs`) always, and the env-gated
243
+ funded micro swap (`scripts/smoke-intents-mainnet.mjs`) per release.
244
+
245
+ ## Upstream
246
+
247
+ - Docs: https://docs.near-intents.org
248
+ - Verifier source: https://github.com/near/intents
249
+ - 1Click OpenAPI: https://1click.chaindefuser.com/docs/v0/openapi.yaml