@gibs/bridge-sdk 1.13.1 → 1.17.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.
Files changed (217) hide show
  1. package/README.md +244 -18
  2. package/abis/package.json +6 -0
  3. package/bridge-limits/package.json +6 -0
  4. package/chain-info/package.json +6 -0
  5. package/chain-names/package.json +6 -0
  6. package/chains/package.json +6 -0
  7. package/claims/package.json +6 -0
  8. package/config/package.json +6 -0
  9. package/consolidated-quote/package.json +6 -0
  10. package/destination-calldata/package.json +6 -0
  11. package/dist/chain-info.d.ts +1 -1
  12. package/dist/chain-info.js +11 -12
  13. package/dist/chains.d.ts +25 -0
  14. package/dist/chains.js +55 -20
  15. package/dist/cjs/abis.d.ts +1 -0
  16. package/dist/cjs/abis.js +19 -0
  17. package/dist/cjs/bridge-limits.d.ts +103 -0
  18. package/dist/cjs/bridge-limits.js +89 -0
  19. package/dist/cjs/chain-info.d.ts +169 -0
  20. package/dist/cjs/chain-info.js +452 -0
  21. package/dist/cjs/chain-names.d.ts +160 -0
  22. package/dist/cjs/chain-names.js +207 -0
  23. package/dist/cjs/chains.d.ts +90 -0
  24. package/dist/cjs/chains.js +357 -0
  25. package/dist/cjs/claims.d.ts +198 -0
  26. package/dist/cjs/claims.js +182 -0
  27. package/dist/cjs/config.d.ts +240 -0
  28. package/dist/cjs/config.js +537 -0
  29. package/dist/cjs/consolidated-quote.d.ts +648 -0
  30. package/dist/cjs/consolidated-quote.js +1048 -0
  31. package/dist/cjs/destination-calldata.d.ts +118 -0
  32. package/dist/cjs/destination-calldata.js +156 -0
  33. package/dist/cjs/ecosystems.d.ts +217 -0
  34. package/dist/cjs/ecosystems.js +298 -0
  35. package/dist/cjs/execution.d.ts +346 -0
  36. package/dist/cjs/execution.js +468 -0
  37. package/dist/cjs/fee-type.d.ts +10 -0
  38. package/dist/cjs/fee-type.js +10 -0
  39. package/dist/cjs/hyperliquid-execution.d.ts +152 -0
  40. package/dist/cjs/hyperliquid-execution.js +175 -0
  41. package/dist/cjs/image-links.d.ts +117 -0
  42. package/dist/cjs/image-links.js +153 -0
  43. package/dist/cjs/index.d.ts +23 -0
  44. package/dist/cjs/index.js +39 -0
  45. package/dist/cjs/lifi-execution-guard.d.ts +210 -0
  46. package/dist/cjs/lifi-execution-guard.js +409 -0
  47. package/dist/cjs/near-intents-conformance.d.ts +85 -0
  48. package/dist/cjs/near-intents-conformance.js +337 -0
  49. package/dist/cjs/near-intents-route.d.ts +47 -0
  50. package/dist/cjs/near-intents-route.js +303 -0
  51. package/dist/cjs/near-intents.d.ts +524 -0
  52. package/dist/cjs/near-intents.js +1071 -0
  53. package/dist/cjs/origin-deposit.d.ts +326 -0
  54. package/dist/cjs/origin-deposit.js +82 -0
  55. package/dist/cjs/package.json +3 -0
  56. package/dist/cjs/pre-quote.d.ts +198 -0
  57. package/dist/cjs/pre-quote.js +180 -0
  58. package/dist/cjs/prices.d.ts +107 -0
  59. package/dist/cjs/prices.js +172 -0
  60. package/dist/cjs/providers.d.ts +238 -0
  61. package/dist/cjs/providers.js +171 -0
  62. package/dist/cjs/quote-fees.d.ts +102 -0
  63. package/dist/cjs/quote-fees.js +201 -0
  64. package/dist/cjs/referral.d.ts +85 -0
  65. package/dist/cjs/referral.js +85 -0
  66. package/dist/cjs/relay-currencies.d.ts +47 -0
  67. package/dist/cjs/relay-currencies.js +95 -0
  68. package/dist/cjs/relay-depository.d.ts +2 -0
  69. package/dist/cjs/relay-depository.js +85 -0
  70. package/dist/cjs/relay-quote.d.ts +301 -0
  71. package/dist/cjs/relay-quote.js +996 -0
  72. package/dist/cjs/route-steps.d.ts +138 -0
  73. package/dist/cjs/route-steps.js +310 -0
  74. package/dist/cjs/routing/amounts.d.ts +80 -0
  75. package/dist/cjs/routing/amounts.js +126 -0
  76. package/dist/cjs/routing/candidate-route.d.ts +43 -0
  77. package/dist/cjs/routing/candidate-route.js +50 -0
  78. package/dist/cjs/routing/candidate.d.ts +237 -0
  79. package/dist/cjs/routing/candidate.js +100 -0
  80. package/dist/cjs/routing/carriers/ask-answer.d.ts +77 -0
  81. package/dist/cjs/routing/carriers/ask-answer.js +2 -0
  82. package/dist/cjs/routing/carriers/omnibridge.d.ts +137 -0
  83. package/dist/cjs/routing/carriers/omnibridge.js +459 -0
  84. package/dist/cjs/routing/carriers/pulsex-paths.d.ts +80 -0
  85. package/dist/cjs/routing/carriers/pulsex-paths.js +97 -0
  86. package/dist/cjs/routing/carriers/pulsex-split.d.ts +175 -0
  87. package/dist/cjs/routing/carriers/pulsex-split.js +193 -0
  88. package/dist/cjs/routing/carriers/pulsex.d.ts +159 -0
  89. package/dist/cjs/routing/carriers/pulsex.js +565 -0
  90. package/dist/cjs/routing/carriers/wrap.d.ts +25 -0
  91. package/dist/cjs/routing/carriers/wrap.js +119 -0
  92. package/dist/cjs/routing/composed-route-quality.d.ts +125 -0
  93. package/dist/cjs/routing/composed-route-quality.js +144 -0
  94. package/dist/cjs/routing/entry-chain-anchor.d.ts +88 -0
  95. package/dist/cjs/routing/entry-chain-anchor.js +59 -0
  96. package/dist/cjs/routing/format.d.ts +31 -0
  97. package/dist/cjs/routing/format.js +64 -0
  98. package/dist/cjs/routing/index.d.ts +38 -0
  99. package/dist/cjs/routing/index.js +134 -0
  100. package/dist/cjs/routing/producers.d.ts +770 -0
  101. package/dist/cjs/routing/producers.js +1617 -0
  102. package/dist/cjs/routing/providers.d.ts +31 -0
  103. package/dist/cjs/routing/providers.js +48 -0
  104. package/dist/cjs/routing/route-data.d.ts +355 -0
  105. package/dist/cjs/routing/route-data.js +2 -0
  106. package/dist/cjs/routing/search/compose.d.ts +168 -0
  107. package/dist/cjs/routing/search/compose.js +104 -0
  108. package/dist/cjs/routing/search/graph.d.ts +668 -0
  109. package/dist/cjs/routing/search/graph.js +461 -0
  110. package/dist/cjs/routing/search/plan-route-data.d.ts +269 -0
  111. package/dist/cjs/routing/search/plan-route-data.js +848 -0
  112. package/dist/cjs/routing/search/score.d.ts +356 -0
  113. package/dist/cjs/routing/search/score.js +445 -0
  114. package/dist/cjs/routing/swap-transactions.d.ts +91 -0
  115. package/dist/cjs/routing/swap-transactions.js +131 -0
  116. package/dist/cjs/routing/usd-value.d.ts +17 -0
  117. package/dist/cjs/routing/usd-value.js +29 -0
  118. package/dist/cjs/settings.d.ts +785 -0
  119. package/dist/cjs/settings.js +1074 -0
  120. package/dist/cjs/ton-connect.d.ts +98 -0
  121. package/dist/cjs/ton-connect.js +206 -0
  122. package/dist/cjs/ton-execution.d.ts +7 -0
  123. package/dist/cjs/ton-execution.js +23 -0
  124. package/dist/cjs/ton.d.ts +37 -0
  125. package/dist/cjs/ton.js +83 -0
  126. package/dist/cjs/tron-execution.d.ts +146 -0
  127. package/dist/cjs/tron-execution.js +248 -0
  128. package/dist/cjs/types.d.ts +51 -0
  129. package/dist/cjs/types.js +2 -0
  130. package/dist/claims.d.ts +198 -0
  131. package/dist/claims.js +140 -0
  132. package/dist/config.d.ts +24 -0
  133. package/dist/config.js +39 -5
  134. package/dist/consolidated-quote.d.ts +109 -15
  135. package/dist/consolidated-quote.js +310 -52
  136. package/dist/execution.d.ts +346 -0
  137. package/dist/execution.js +428 -0
  138. package/dist/hyperliquid-execution.js +2 -3
  139. package/dist/index.d.ts +3 -0
  140. package/dist/index.js +3 -0
  141. package/dist/lifi-execution-guard.d.ts +13 -2
  142. package/dist/lifi-execution-guard.js +4 -0
  143. package/dist/near-intents.js +2 -3
  144. package/dist/pre-quote.d.ts +6 -6
  145. package/dist/pre-quote.js +10 -14
  146. package/dist/providers.d.ts +17 -0
  147. package/dist/providers.js +6 -0
  148. package/dist/referral.d.ts +12 -4
  149. package/dist/referral.js +12 -4
  150. package/dist/relay-depository.d.ts +2 -0
  151. package/dist/relay-depository.js +82 -0
  152. package/dist/relay-quote.js +2 -3
  153. package/dist/routing/candidate.d.ts +3 -3
  154. package/dist/routing/candidate.js +3 -3
  155. package/dist/routing/carriers/ask-answer.d.ts +17 -0
  156. package/dist/routing/carriers/omnibridge.js +23 -21
  157. package/dist/routing/carriers/pulsex-paths.d.ts +80 -0
  158. package/dist/routing/carriers/pulsex-paths.js +92 -0
  159. package/dist/routing/carriers/pulsex-split.d.ts +175 -0
  160. package/dist/routing/carriers/pulsex-split.js +185 -0
  161. package/dist/routing/carriers/pulsex.d.ts +31 -21
  162. package/dist/routing/carriers/pulsex.js +141 -71
  163. package/dist/routing/composed-route-quality.d.ts +28 -6
  164. package/dist/routing/composed-route-quality.js +36 -5
  165. package/dist/routing/entry-chain-anchor.d.ts +1 -1
  166. package/dist/routing/entry-chain-anchor.js +1 -1
  167. package/dist/routing/index.d.ts +6 -2
  168. package/dist/routing/index.js +6 -2
  169. package/dist/routing/producers.js +1 -1
  170. package/dist/routing/providers.d.ts +5 -0
  171. package/dist/routing/providers.js +5 -0
  172. package/dist/routing/search/graph.d.ts +15 -0
  173. package/dist/routing/search/plan-route-data.d.ts +25 -0
  174. package/dist/routing/search/plan-route-data.js +43 -7
  175. package/dist/routing/search/score.d.ts +3 -0
  176. package/dist/routing/search/score.js +11 -3
  177. package/dist/routing/swap-transactions.d.ts +91 -0
  178. package/dist/routing/swap-transactions.js +125 -0
  179. package/dist/settings.d.ts +94 -1
  180. package/dist/settings.js +95 -3
  181. package/dist/ton-connect.d.ts +98 -0
  182. package/dist/ton-connect.js +200 -0
  183. package/dist/ton-execution.d.ts +4 -114
  184. package/dist/ton-execution.js +4 -244
  185. package/dist/ton.d.ts +37 -0
  186. package/dist/ton.js +65 -0
  187. package/docs/examples/example-a-ether-to-pulse.ts +300 -0
  188. package/docs/examples/example-b-token-pulsechain-to-ethereum.ts +270 -0
  189. package/docs/examples/shared.ts +261 -0
  190. package/docs/examples/tsconfig.json +18 -0
  191. package/docs/integration-guide.md +617 -0
  192. package/ecosystems/package.json +6 -0
  193. package/execution/package.json +6 -0
  194. package/fee-type/package.json +6 -0
  195. package/hyperliquid-execution/package.json +6 -0
  196. package/image-links/package.json +6 -0
  197. package/lifi-execution-guard/package.json +6 -0
  198. package/llms.txt +75 -10
  199. package/near-intents/package.json +6 -0
  200. package/near-intents-route/package.json +6 -0
  201. package/origin-deposit/package.json +6 -0
  202. package/package.json +817 -10
  203. package/pre-quote/package.json +6 -0
  204. package/prices/package.json +6 -0
  205. package/providers/package.json +6 -0
  206. package/quote-fees/package.json +6 -0
  207. package/referral/package.json +6 -0
  208. package/relay-currencies/package.json +6 -0
  209. package/relay-quote/package.json +6 -0
  210. package/route-steps/package.json +6 -0
  211. package/routing/package.json +6 -0
  212. package/settings/package.json +6 -0
  213. package/ton/package.json +6 -0
  214. package/ton-connect/package.json +6 -0
  215. package/ton-execution/package.json +6 -0
  216. package/tron-execution/package.json +6 -0
  217. package/types/package.json +6 -0
@@ -1,247 +1,7 @@
1
- import { Cell, beginCell, loadMessage, storeMessage } from '@ton/core';
2
1
  /**
3
- * Executing a deposit on The Open Network, as pure data.
2
+ * The original name of `@gibs/bridge-sdk/ton`, kept so existing imports work.
4
3
  *
5
- * Relay describes a deposit as a list of internal messages carrying `to`,
6
- * `value`, `body` and a `bounce` flag. A connected wallet, however, is driven
7
- * through the TON Connect protocol, whose request shape is different in one way
8
- * that silently loses money:
9
- *
10
- * **TON Connect has no per-message `bounce` field.** A wallet derives whether a
11
- * message bounces from the FORM OF THE ADDRESS — the bounceable `EQ…` prefix or
12
- * the non-bounceable `UQ…` one. Passing Relay's address through unchanged while
13
- * dropping its `bounce: false` therefore sends a deposit to the bounceable form,
14
- * and the depository bounces the funds straight back to the sender. The transfer
15
- * looks like it succeeded and the route never fills.
16
- *
17
- * {@link buildTonConnectRequest} folds `bounce` into the address so the flag
18
- * cannot be dropped on the way to a wallet.
19
- *
20
- * Kept free of transport and of clock reads so it is fully testable: nothing
21
- * here signs, fetches, or calls `Date.now`. The caller supplies the deadline and
22
- * performs the request.
23
- *
24
- * The rules encoded here are those of Relay's own wallet adapter, which is the
25
- * only reference implementation of this mapping.
26
- */
27
- /**
28
- * The TON Connect network identifier for mainnet. Relay bridges only mainnet, so
29
- * a request is always tagged with this.
30
- */
31
- export const tonConnectMainnetNetwork = '-239';
32
- /** How long a request stays valid by default, in seconds, matching Relay's adapter. */
33
- export const tonConnectDefaultValidForSeconds = 300;
34
- /** Address tag byte for the bounceable (`EQ…`) form. */
35
- const bounceableTag = 0x11;
36
- /** Address tag byte for the non-bounceable (`UQ…`) form. */
37
- const nonBounceableTag = 0x51;
38
- /** Bit set on the tag byte for testnet-only addresses (`kQ…` / `0Q…`). */
39
- const testnetTagFlag = 0x80;
40
- /** A friendly address is a 36-byte structure: tag, workchain, hash, checksum. */
41
- const friendlyAddressByteLength = 36;
42
- /** An account identifier is always 32 bytes, in every address form. */
43
- const accountHashByteLength = 32;
44
- /**
45
- * Cyclic redundancy check (16-bit, the XModem variant) over an address body.
46
- * The Open Network appends it to every friendly address, and a re-encoded
47
- * address whose checksum is not recomputed is rejected by wallets as malformed.
48
- */
49
- const checksum = (data) => {
50
- let remainder = 0;
51
- for (const byte of data) {
52
- remainder ^= byte << 8;
53
- for (let bit = 0; bit < 8; bit += 1) {
54
- remainder =
55
- remainder & 0x8000 ? ((remainder << 1) ^ 0x1021) & 0xffff : (remainder << 1) & 0xffff;
56
- }
57
- }
58
- return remainder;
59
- };
60
- /** Decodes the url-safe base64 alphabet the friendly address form uses. */
61
- const decodeBase64Url = (value) => {
62
- const standard = value.replaceAll('-', '+').replaceAll('_', '/');
63
- const padded = standard.padEnd(Math.ceil(standard.length / 4) * 4, '=');
64
- try {
65
- const binary = atob(padded);
66
- return Uint8Array.from(binary, (character) => character.charCodeAt(0));
67
- }
68
- catch {
69
- return null;
70
- }
71
- };
72
- /** Encodes bytes back into the url-safe, unpadded base64 form. */
73
- const encodeBase64Url = (bytes) => {
74
- let binary = '';
75
- for (const byte of bytes) {
76
- binary += String.fromCharCode(byte);
77
- }
78
- return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
79
- };
80
- const parseRawAddress = (value) => {
81
- const separator = value.indexOf(':');
82
- if (separator < 0) {
83
- return null;
84
- }
85
- const workchain = Number(value.slice(0, separator));
86
- const hex = value.slice(separator + 1);
87
- if (!Number.isInteger(workchain) || hex.length !== accountHashByteLength * 2) {
88
- return null;
89
- }
90
- const hash = new Uint8Array(accountHashByteLength);
91
- for (let index = 0; index < accountHashByteLength; index += 1) {
92
- const byte = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
93
- if (Number.isNaN(byte)) {
94
- return null;
95
- }
96
- hash[index] = byte;
97
- }
98
- // The raw form carries no network tag, so mainnet is the only safe assumption.
99
- return { workchain, hash, testOnly: false };
100
- };
101
- const parseFriendlyAddress = (value) => {
102
- const bytes = decodeBase64Url(value);
103
- if (bytes === null || bytes.length !== friendlyAddressByteLength) {
104
- return null;
105
- }
106
- const body = bytes.subarray(0, friendlyAddressByteLength - 2);
107
- // Every index below is in range: the length was checked to be exactly
108
- // `friendlyAddressByteLength` above, so the non-null assertions are sound.
109
- const stated = (bytes[friendlyAddressByteLength - 2] << 8) | bytes[friendlyAddressByteLength - 1];
110
- if (checksum(body) !== stated) {
111
- return null;
112
- }
113
- const tag = bytes[0];
114
- const withoutTestFlag = tag & ~testnetTagFlag;
115
- if (withoutTestFlag !== bounceableTag && withoutTestFlag !== nonBounceableTag) {
116
- return null;
117
- }
118
- const workchainByte = bytes[1];
119
- return {
120
- // The masterchain is workchain -1, which travels as the byte 0xff.
121
- workchain: workchainByte === 0xff ? -1 : workchainByte,
122
- hash: bytes.slice(2, 2 + accountHashByteLength),
123
- testOnly: (tag & testnetTagFlag) !== 0,
124
- };
125
- };
126
- /**
127
- * Parses either address form The Open Network uses.
128
- *
129
- * @param value - a friendly (`EQ…`/`UQ…`) or raw (`0:…`) address
130
- * @returns the address parts, or null when it is neither form or fails its checksum
131
- */
132
- export const parseTonAddress = (value) => value.includes(':') ? parseRawAddress(value) : parseFriendlyAddress(value);
133
- /**
134
- * Re-encodes an address into the friendly form matching a bounce setting.
135
- *
136
- * This is the whole point of the module: the bounce flag has nowhere else to
137
- * live in a TON Connect request, so it has to be carried by the address itself.
138
- *
139
- * @param address - the address in either form
140
- * @param options.bounceable - whether the message may bounce back to the sender
141
- * @returns the friendly address, or null when the input cannot be parsed
142
- */
143
- export const encodeTonAddress = (address, { bounceable }) => {
144
- const parsed = parseTonAddress(address);
145
- if (parsed === null) {
146
- return null;
147
- }
148
- const bytes = new Uint8Array(friendlyAddressByteLength);
149
- bytes[0] =
150
- (bounceable ? bounceableTag : nonBounceableTag) | (parsed.testOnly ? testnetTagFlag : 0);
151
- bytes[1] = parsed.workchain < 0 ? 0xff : parsed.workchain;
152
- bytes.set(parsed.hash, 2);
153
- const stated = checksum(bytes.subarray(0, friendlyAddressByteLength - 2));
154
- bytes[friendlyAddressByteLength - 2] = (stated >> 8) & 0xff;
155
- bytes[friendlyAddressByteLength - 1] = stated & 0xff;
156
- return encodeBase64Url(bytes);
157
- };
158
- /**
159
- * Builds the TON Connect request for a deposit.
160
- *
161
- * @param deposit - the parsed origin deposit
162
- * @param options.validUntil - unix seconds after which the request expires.
163
- * Supplied by the caller rather than read from a clock here so the transform
164
- * stays pure; {@link tonConnectDefaultValidForSeconds} is the usual offset.
165
- * @returns the request, or null when the deposit carries no messages or any
166
- * destination address cannot be re-encoded — never a request whose bounce
167
- * setting has been silently dropped
168
- */
169
- export const buildTonConnectRequest = (deposit, { validUntil }) => {
170
- if (deposit.messages.length === 0) {
171
- return null;
172
- }
173
- const messages = [];
174
- for (const message of deposit.messages) {
175
- const address = encodeTonAddress(message.to, { bounceable: message.bounce });
176
- // Refuse rather than fall back to the unencoded address: the fallback is
177
- // exactly the bounce-losing behaviour this module exists to prevent.
178
- if (address === null) {
179
- return null;
180
- }
181
- messages.push({
182
- address,
183
- amount: message.value.toString(),
184
- ...(message.body ? { payload: message.body } : {}),
185
- });
186
- }
187
- // `messages[0]` is present: the empty case returned null above.
188
- const from = deposit.messages[0].from;
189
- return {
190
- validUntil,
191
- network: tonConnectMainnetNetwork,
192
- ...(from === null ? {} : { from }),
193
- messages,
194
- };
195
- };
196
- /**
197
- * Derives the transaction identifier from a wallet's `sendTransaction` response.
198
- *
199
- * A wallet answers with a `boc`: the base64 bag-of-cells of the signed
200
- * external-in message it broadcast — NOT the resulting transaction, and NOT a
201
- * hash. The identifier explorers and indexers (toncenter, TonAPI) resolve a
202
- * transaction by is the hash of that external message, but it must be the
203
- * NORMALIZED hash of TEP-467, not the hash of the bytes the wallet happened to
204
- * send.
205
- *
206
- * The distinction is the whole point, and getting it wrong loses a resume record
207
- * for real funds. An external-in message can be serialized several equivalent
208
- * ways — a wallet may or may not include its own `src`, may set a non-zero
209
- * `import_fee`, may attach a `stateInit`, may store the body inline or as a
210
- * reference. The raw cell hash changes with each of those wallet-specific
211
- * choices, so it frequently does NOT match what an indexer keyed the same
212
- * transaction under. TEP-467 rebuilds the message with those variable fields
213
- * stripped (`src` to none, `import_fee` to zero, `init` dropped, body forced to a
214
- * reference) so the hash is stable across wallets and matches the indexers.
215
- *
216
- * @param boc - the wallet's base64 `boc` field from `sendTransaction`
217
- * @returns the 64-character hex normalized hash, or null when the input is not a
218
- * parseable external-in message
4
+ * @deprecated Import from `@gibs/bridge-sdk/ton` for the full TON surface, or
5
+ * from `@gibs/bridge-sdk/ton-connect` for the helpers that need no TON library.
219
6
  */
220
- export const tonExternalMessageHash = (boc) => {
221
- try {
222
- const message = loadMessage(Cell.fromBase64(boc).beginParse());
223
- // Only an external-in message has a normalized form; an internal message or
224
- // a bare cell is not the wallet's broadcast envelope and cannot be keyed.
225
- if (message.info.type !== 'external-in') {
226
- return null;
227
- }
228
- const normalized = {
229
- ...message,
230
- // Dropped by TEP-467 so a wallet-attached stateInit does not move the hash.
231
- init: null,
232
- // `src` to addr_none (undefined) and the fee to zero: both are the sender's
233
- // to choose and neither is part of what identifies the message.
234
- info: { ...message.info, src: undefined, importFee: 0n },
235
- };
236
- // `forceRef` stores the body as a reference, as the normalized form requires.
237
- return beginCell()
238
- .store(storeMessage(normalized, { forceRef: true }))
239
- .endCell()
240
- .hash()
241
- .toString('hex');
242
- }
243
- catch {
244
- // A malformed base64 payload or a cell tree that is not a message at all.
245
- return null;
246
- }
247
- };
7
+ export * from './ton.js';
package/dist/ton.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Everything the bridge SDK offers for The Open Network, in one entry point.
3
+ *
4
+ * It re-exports the pure TON Connect helpers from `./ton-connect.js` and adds
5
+ * {@link tonExternalMessageHash}, the only helper that needs `@ton/core`.
6
+ *
7
+ * Kept apart from the root entry point on purpose. `@ton/core` uses Node's
8
+ * `Buffer`, and its `@ton/crypto` dependency resolves, under React Native, to a
9
+ * build that requires `expo-crypto`. An application that bridges only between
10
+ * Ethereum-style chains never imports this file, so it never pays for either.
11
+ */
12
+ export * from './ton-connect.js';
13
+ /**
14
+ * Derives the transaction identifier from a wallet's `sendTransaction` response.
15
+ *
16
+ * A wallet answers with a `boc`: the base64 bag-of-cells of the signed
17
+ * external-in message it broadcast — NOT the resulting transaction, and NOT a
18
+ * hash. The identifier explorers and indexers (toncenter, TonAPI) resolve a
19
+ * transaction by is the hash of that external message, but it must be the
20
+ * NORMALIZED hash of TEP-467, not the hash of the bytes the wallet happened to
21
+ * send.
22
+ *
23
+ * The distinction is the whole point, and getting it wrong loses a resume record
24
+ * for real funds. An external-in message can be serialized several equivalent
25
+ * ways — a wallet may or may not include its own `src`, may set a non-zero
26
+ * `import_fee`, may attach a `stateInit`, may store the body inline or as a
27
+ * reference. The raw cell hash changes with each of those wallet-specific
28
+ * choices, so it frequently does NOT match what an indexer keyed the same
29
+ * transaction under. TEP-467 rebuilds the message with those variable fields
30
+ * stripped (`src` to none, `import_fee` to zero, `init` dropped, body forced to a
31
+ * reference) so the hash is stable across wallets and matches the indexers.
32
+ *
33
+ * @param boc - the wallet's base64 `boc` field from `sendTransaction`
34
+ * @returns the 64-character hex normalized hash, or null when the input is not a
35
+ * parseable external-in message
36
+ */
37
+ export declare const tonExternalMessageHash: (boc: string) => string | null;
package/dist/ton.js ADDED
@@ -0,0 +1,65 @@
1
+ import { Cell, beginCell, loadMessage, storeMessage } from '@ton/core';
2
+ /**
3
+ * Everything the bridge SDK offers for The Open Network, in one entry point.
4
+ *
5
+ * It re-exports the pure TON Connect helpers from `./ton-connect.js` and adds
6
+ * {@link tonExternalMessageHash}, the only helper that needs `@ton/core`.
7
+ *
8
+ * Kept apart from the root entry point on purpose. `@ton/core` uses Node's
9
+ * `Buffer`, and its `@ton/crypto` dependency resolves, under React Native, to a
10
+ * build that requires `expo-crypto`. An application that bridges only between
11
+ * Ethereum-style chains never imports this file, so it never pays for either.
12
+ */
13
+ export * from './ton-connect.js';
14
+ /**
15
+ * Derives the transaction identifier from a wallet's `sendTransaction` response.
16
+ *
17
+ * A wallet answers with a `boc`: the base64 bag-of-cells of the signed
18
+ * external-in message it broadcast — NOT the resulting transaction, and NOT a
19
+ * hash. The identifier explorers and indexers (toncenter, TonAPI) resolve a
20
+ * transaction by is the hash of that external message, but it must be the
21
+ * NORMALIZED hash of TEP-467, not the hash of the bytes the wallet happened to
22
+ * send.
23
+ *
24
+ * The distinction is the whole point, and getting it wrong loses a resume record
25
+ * for real funds. An external-in message can be serialized several equivalent
26
+ * ways — a wallet may or may not include its own `src`, may set a non-zero
27
+ * `import_fee`, may attach a `stateInit`, may store the body inline or as a
28
+ * reference. The raw cell hash changes with each of those wallet-specific
29
+ * choices, so it frequently does NOT match what an indexer keyed the same
30
+ * transaction under. TEP-467 rebuilds the message with those variable fields
31
+ * stripped (`src` to none, `import_fee` to zero, `init` dropped, body forced to a
32
+ * reference) so the hash is stable across wallets and matches the indexers.
33
+ *
34
+ * @param boc - the wallet's base64 `boc` field from `sendTransaction`
35
+ * @returns the 64-character hex normalized hash, or null when the input is not a
36
+ * parseable external-in message
37
+ */
38
+ export const tonExternalMessageHash = (boc) => {
39
+ try {
40
+ const message = loadMessage(Cell.fromBase64(boc).beginParse());
41
+ // Only an external-in message has a normalized form; an internal message or
42
+ // a bare cell is not the wallet's broadcast envelope and cannot be keyed.
43
+ if (message.info.type !== 'external-in') {
44
+ return null;
45
+ }
46
+ const normalized = {
47
+ ...message,
48
+ // Dropped by TEP-467 so a wallet-attached stateInit does not move the hash.
49
+ init: null,
50
+ // `src` to addr_none (undefined) and the fee to zero: both are the sender's
51
+ // to choose and neither is part of what identifies the message.
52
+ info: { ...message.info, src: undefined, importFee: 0n },
53
+ };
54
+ // `forceRef` stores the body as a reference, as the normalized form requires.
55
+ return beginCell()
56
+ .store(storeMessage(normalized, { forceRef: true }))
57
+ .endCell()
58
+ .hash()
59
+ .toString('hex');
60
+ }
61
+ catch {
62
+ // A malformed base64 payload or a cell tree that is not a message at all.
63
+ return null;
64
+ }
65
+ };
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Example A: ETH on Ethereum to PLS on PulseChain.
3
+ *
4
+ * 1. Build viem clients from the wallet's own RPC endpoints.
5
+ * 2. Search with `searchBridgeRoutes` and pick the best served route.
6
+ * 3. Plan ONE section at a time with `planCandidateExecution`
7
+ * (`firstSectionIndex`), so a later section spends what actually arrived,
8
+ * with a fresh swap floor and deadline, or a fresh aggregator quote.
9
+ * 4. Send each section's transactions, honouring `waitForReceipt`, and wait
10
+ * for the crossing to arrive before planning the next section.
11
+ * 5. Poll `fetchTransferStatus` until the last crossing is delivered.
12
+ *
13
+ * Typechecked only (`docs/examples/tsconfig.json` in `@gibs/bridge-sdk`). Never run against mainnet.
14
+ */
15
+ import type { AggregatorStatusSource, FetchTransferStatusOptions } from '@gibs/bridge-indexer';
16
+ import { tokenBridgeInfo } from '@gibs/bridge-sdk/chain-info';
17
+ import { type Provider, Providers, toChain } from '@gibs/bridge-sdk/config';
18
+ import type { ConsolidatedQuote } from '@gibs/bridge-sdk/consolidated-quote';
19
+ import {
20
+ type CandidateExecutionPlan,
21
+ planCandidateExecution,
22
+ readPulseXSectionQuote,
23
+ type SectionExecutionInputs,
24
+ } from '@gibs/bridge-sdk/execution';
25
+ import type { PlannedHop, ResolveNodeToken, RoutePlan } from '@gibs/bridge-sdk/routing';
26
+ import { fetchLiveSectionQuote, type ServedBridgeRoute, searchBridgeRoutes } from '@gibs/quotes/search';
27
+ import { type Hex, type PublicClient, zeroAddress } from 'viem';
28
+ import {
29
+ allowanceReaderWith,
30
+ createClientLookup,
31
+ deadlineIn,
32
+ type RpcUrlByChainId,
33
+ readBalance,
34
+ requireClientWith,
35
+ type SentTransaction,
36
+ sendPlannedTransactions,
37
+ type WalletSigner,
38
+ waitForTransferStatus,
39
+ } from './shared.js';
40
+
41
+ const ethereumChainId = 1;
42
+ const pulsechainChainId = 369;
43
+
44
+ /** What the example needs from the wallet. */
45
+ export type EtherToPulseOptions = {
46
+ readonly signer: WalletSigner;
47
+ readonly rpcUrlByChainId: RpcUrlByChainId;
48
+ /** The ether to send, in wei. */
49
+ readonly amountWei: bigint;
50
+ /** Cancels the search, every live quote and every status poll. */
51
+ readonly signal: AbortSignal;
52
+ /** The wallet's own LI.FI key, sent only as the `x-lifi-api-key` header. Leave it out to use LI.FI's keyless tier. */
53
+ readonly lifiApiKey?: string;
54
+ /** The wallet's own Relay key, sent only as a header. Leave it out to use Relay's anonymous tier. */
55
+ readonly relayApiKey?: string;
56
+ };
57
+
58
+ const omnibridgeCarriers: ReadonlySet<string> = new Set(Object.values(Providers));
59
+
60
+ const isOmnibridgeCarrier = (carrier: string): carrier is Provider =>
61
+ omnibridgeCarriers.has(carrier);
62
+
63
+ /** A plan's sections, one per signature, each as its hops. */
64
+ const sectionsOf = (plan: RoutePlan): readonly (readonly PlannedHop[])[] =>
65
+ plan.signatures.map((group) => group.flatMap((hopIndex) => plan.hops[hopIndex] ?? []));
66
+
67
+ /** Where `fetchTransferStatus` asks about an aggregator's leg. */
68
+ const aggregatorSourceOf = (
69
+ quote: ConsolidatedQuote,
70
+ relayApiKey: string | undefined,
71
+ ): AggregatorStatusSource => {
72
+ if (quote.provider === 'lifi') return { provider: 'lifi' };
73
+ if (quote.provider === 'relay') {
74
+ return {
75
+ provider: 'relay',
76
+ requestId: quote.quoteId,
77
+ ...(relayApiKey === undefined ? {} : { apiKey: relayApiKey }),
78
+ };
79
+ }
80
+ return { provider: 'near-intents', depositAddress: quote.quoteId };
81
+ };
82
+
83
+ /** One section's inputs, and the live quote it signs when an aggregator carries it. */
84
+ type SectionPlanInputs = {
85
+ readonly inputs: SectionExecutionInputs;
86
+ readonly liveQuote: ConsolidatedQuote | null;
87
+ };
88
+
89
+ /**
90
+ * Builds the inputs for ONE section, from what it spends NOW: the typed
91
+ * amount for the first section, what actually arrived for a later one.
92
+ */
93
+ const buildSectionInputs = async (options: {
94
+ readonly served: ServedBridgeRoute;
95
+ readonly sectionIndex: number;
96
+ readonly hops: readonly PlannedHop[];
97
+ readonly amountIn: bigint;
98
+ readonly resolveToken: ResolveNodeToken;
99
+ readonly account: Hex;
100
+ readonly requireClient: (chainId: number) => PublicClient;
101
+ readonly clientForChain: (chainId: number) => PublicClient | null;
102
+ readonly signal: AbortSignal;
103
+ readonly lifiApiKey: string | undefined;
104
+ readonly relayApiKey: string | undefined;
105
+ }): Promise<SectionPlanInputs> => {
106
+ const { served, sectionIndex, hops, amountIn, resolveToken, account, requireClient } = options;
107
+ const firstHop = hops[0];
108
+ if (firstHop === undefined) throw new Error(`section ${sectionIndex} has no hops`);
109
+ // Every section delivers to the signer; this example sends to the signer too.
110
+ const recipient = account;
111
+ const { carrier, from, to } = firstHop.edge;
112
+
113
+ if (isOmnibridgeCarrier(carrier)) {
114
+ // How the token maps across the bridge. Native ether reads the wrapped-ether mapping.
115
+ const assetLink = await tokenBridgeInfo({
116
+ bridgeKey: [carrier, toChain(from.chainId), toChain(to.chainId)],
117
+ assetIn: resolveToken(from),
118
+ isProd: true,
119
+ fromChainClient: requireClient(from.chainId),
120
+ toChainClient: requireClient(to.chainId),
121
+ });
122
+ if (assetLink === null)
123
+ throw new Error(`no omnibridge mapping for ${from.address} on chain ${from.chainId}`);
124
+ // The explicit delivery choice. Entering PulseChain, the bridge itself
125
+ // executes the crossing, so `shouldDeliver: false` is the only accepted
126
+ // choice here. Leaving PulseChain, see Example B.
127
+ const omnibridge = { isProd: true, assetLink, delivery: { shouldDeliver: false }, unwrap: false } as const;
128
+ return { inputs: { amountIn, recipient, omnibridge }, liveQuote: null };
129
+ }
130
+ if (carrier === 'PulseX') {
131
+ // A FRESH QUOTE, read now along the exact path the swap signs, for the
132
+ // amount that actually arrived. The floor is one percent below it, and the
133
+ // deadline starts now, not before the crossing.
134
+ const quotedAmountOut = await readPulseXSectionQuote({
135
+ hop: firstHop,
136
+ amountIn,
137
+ client: requireClient(from.chainId),
138
+ });
139
+ if (quotedAmountOut === null) throw new Error(`section ${sectionIndex}: PulseX gave no quote`);
140
+ return {
141
+ inputs: { amountIn, recipient, swap: { slippageBasisPoints: 100n, deadline: deadlineIn(20 * 60), quotedAmountOut } },
142
+ liveQuote: null,
143
+ };
144
+ }
145
+ if (carrier === 'wrap') return { inputs: { amountIn, recipient }, liveQuote: null };
146
+ // Any other carrier is an aggregator. It signs only a LIVE quote, asked now
147
+ // for this wallet, this recipient and this amount.
148
+ const live = await fetchLiveSectionQuote({
149
+ plan: served.plan,
150
+ sectionIndex,
151
+ resolveToken,
152
+ amountIn,
153
+ account,
154
+ recipient,
155
+ clientForChain: options.clientForChain,
156
+ fetch: (input, init) => globalThis.fetch(input, init),
157
+ signal: options.signal,
158
+ keys: {
159
+ ...(options.lifiApiKey === undefined ? {} : { lifiApiKey: options.lifiApiKey }),
160
+ ...(options.relayApiKey === undefined ? {} : { relayApiKey: options.relayApiKey }),
161
+ },
162
+ nearIntentsUserAgent: 'your-wallet/1.0',
163
+ });
164
+ if (live.kind !== 'quoted') throw new Error(`section ${sectionIndex}: no live quote (${live.kind})`);
165
+ return { inputs: { amountIn, recipient, aggregator: { quote: live.quote, request: live.request } }, liveQuote: live.quote };
166
+ };
167
+
168
+ /** Turns a refused plan into an error a person can act on. */
169
+ const assertPlanned = (
170
+ plan: CandidateExecutionPlan,
171
+ ): Extract<CandidateExecutionPlan, { kind: 'planned' }> => {
172
+ if (plan.kind === 'planned') return plan;
173
+ throw new Error(
174
+ `section ${plan.sectionIndex} cannot be planned: ${JSON.stringify(plan.refusal, (_key, value) => (typeof value === 'bigint' ? value.toString() : value))}`,
175
+ );
176
+ };
177
+
178
+ /**
179
+ * Sends ether from Ethereum and delivers Pulse on PulseChain.
180
+ *
181
+ * @param options - see {@link EtherToPulseOptions}
182
+ * @returns the transactions sent, and the final status of the last crossing
183
+ */
184
+ export const sendEtherToPulse = async (options: EtherToPulseOptions) => {
185
+ const { signer, signal } = options;
186
+ const clientForChain = createClientLookup(options.rpcUrlByChainId);
187
+ const requireClient = requireClientWith(clientForChain);
188
+
189
+ // 1. Search. No attribution and no fee: the wallet names none here.
190
+ const search = await searchBridgeRoutes({
191
+ origin: {
192
+ chainId: ethereumChainId,
193
+ token: { address: zeroAddress, decimals: 18, symbol: 'ETH' },
194
+ amount: options.amountWei,
195
+ account: signer.address,
196
+ },
197
+ destination: {
198
+ chainId: pulsechainChainId,
199
+ token: { address: zeroAddress, decimals: 18, symbol: 'PLS' },
200
+ recipient: signer.address,
201
+ },
202
+ clientForChain,
203
+ fetch: (input, init) => globalThis.fetch(input, init),
204
+ signal,
205
+ timeouts: { requestMs: 15_000, searchMs: 60_000 },
206
+ ...(options.lifiApiKey === undefined ? {} : { lifiApiKey: options.lifiApiKey }),
207
+ ...(options.relayApiKey === undefined ? {} : { relayApiKey: options.relayApiKey }),
208
+ nearIntentsUserAgent: 'your-wallet/1.0',
209
+ });
210
+
211
+ // 2. Pick a route. `routes` is best first, one per provider.
212
+ const served = search.routes[0];
213
+ if (served === undefined) {
214
+ const why =
215
+ search.priceImpactRefusal === null ? `${search.refusals.length} refusals` : 'price impact';
216
+ throw new Error(`no route can be served (${why})`);
217
+ }
218
+ const hopsBySection = sectionsOf(served.plan);
219
+
220
+ const statusOptionsFor = (
221
+ crossing: SentTransaction,
222
+ liveQuote: ConsolidatedQuote | null,
223
+ ): FetchTransferStatusOptions => ({
224
+ signal,
225
+ originClient: requireClient(crossing.transaction.chainId),
226
+ ...(liveQuote === null ? {} : { aggregator: aggregatorSourceOf(liveQuote, options.relayApiKey) }),
227
+ });
228
+ const waitForDelivered = (crossing: SentTransaction, liveQuote: ConsolidatedQuote | null) =>
229
+ waitForTransferStatus({
230
+ source: { chainId: crossing.transaction.chainId, txHash: crossing.hash },
231
+ statusOptions: statusOptionsFor(crossing, liveQuote),
232
+ isDone: (status) => status.state === 'delivered',
233
+ intervalMs: 15_000,
234
+ });
235
+
236
+ // 3 and 4. One section at a time. The first spends the typed amount; each
237
+ // later one spends what actually arrived from the one before it.
238
+ const sent: SentTransaction[] = [];
239
+ let amountIn = options.amountWei;
240
+ for (const [sectionIndex, hops] of hopsBySection.entries()) {
241
+ const { inputs, liveQuote } = await buildSectionInputs({
242
+ served,
243
+ sectionIndex,
244
+ hops,
245
+ amountIn,
246
+ resolveToken: search.resolveToken,
247
+ account: signer.address,
248
+ requireClient,
249
+ clientForChain,
250
+ signal,
251
+ lifiApiKey: options.lifiApiKey,
252
+ relayApiKey: options.relayApiKey,
253
+ });
254
+ const plan = assertPlanned(
255
+ await planCandidateExecution({
256
+ candidate: served.candidate,
257
+ account: signer.address,
258
+ readAllowance: allowanceReaderWith(requireClient),
259
+ resolveToken: search.resolveToken,
260
+ firstSectionIndex: sectionIndex,
261
+ sections: [inputs],
262
+ }),
263
+ );
264
+ // Show `plan.deliveries` and every fee before the user signs. See "Fees and attribution".
265
+
266
+ // What the next section will spend arrives on its own chain. Read the
267
+ // balance there before this section runs, and again once it arrived.
268
+ const nextHop = hopsBySection[sectionIndex + 1]?.[0];
269
+ const nextToken = nextHop === undefined ? null : search.resolveToken(nextHop.edge.from);
270
+ const readNextBalance = () =>
271
+ nextToken === null
272
+ ? Promise.resolve(0n)
273
+ : readBalance({ client: requireClient(nextToken.chainId), token: nextToken.address, owner: signer.address });
274
+ const balanceBefore = await readNextBalance();
275
+
276
+ // Every transaction of one section is on one chain.
277
+ const sectionSent = await sendPlannedTransactions({
278
+ transactions: plan.transactions,
279
+ signer,
280
+ requireClient,
281
+ waitForArrival: async () => {},
282
+ });
283
+ sent.push(...sectionSent);
284
+ const last = sectionSent.at(-1);
285
+ if (last === undefined) throw new Error(`section ${sectionIndex} planned no transactions`);
286
+ const receipt = await requireClient(last.transaction.chainId).waitForTransactionReceipt({ hash: last.hash });
287
+ if (receipt.status !== 'success') throw new Error(`the transaction ${last.hash} reverted`);
288
+
289
+ const crossesChains = last.transaction.kind === 'bridge';
290
+ // 5. The route ends with this section. A crossing is done when delivered;
291
+ // a swap is done when its receipt succeeds.
292
+ if (nextToken === null) {
293
+ return { sent, finalStatus: crossesChains ? await waitForDelivered(last, liveQuote) : null };
294
+ }
295
+ if (crossesChains) await waitForDelivered(last, liveQuote);
296
+ amountIn = (await readNextBalance()) - balanceBefore;
297
+ if (amountIn <= 0n) throw new Error(`nothing arrived for section ${sectionIndex + 1}`);
298
+ }
299
+ throw new Error('the route had no sections');
300
+ };