@txnod/sdk 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +29 -0
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +434 -0
- package/dist/_shared/index.d.ts +68 -0
- package/dist/client-sandbox.d.ts +396 -0
- package/dist/client-sandbox.d.ts.map +1 -0
- package/dist/client-sandbox.js +448 -0
- package/dist/client-sandbox.js.map +1 -0
- package/dist/client.d.ts +429 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +588 -0
- package/dist/client.js.map +1 -0
- package/dist/env.d.ts +29 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +44 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +1887 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +2107 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/internals/error-ctor-map.d.ts +11 -0
- package/dist/internals/error-ctor-map.d.ts.map +1 -0
- package/dist/internals/error-ctor-map.js +75 -0
- package/dist/internals/error-ctor-map.js.map +1 -0
- package/dist/internals/fetch-with-retry.d.ts +34 -0
- package/dist/internals/fetch-with-retry.d.ts.map +1 -0
- package/dist/internals/fetch-with-retry.js +233 -0
- package/dist/internals/fetch-with-retry.js.map +1 -0
- package/dist/internals/hmac.d.ts +2 -0
- package/dist/internals/hmac.d.ts.map +1 -0
- package/dist/internals/hmac.js +10 -0
- package/dist/internals/hmac.js.map +1 -0
- package/dist/internals/logger.d.ts +9 -0
- package/dist/internals/logger.d.ts.map +1 -0
- package/dist/internals/logger.js +16 -0
- package/dist/internals/logger.js.map +1 -0
- package/dist/internals/parse-problem-details.d.ts +3 -0
- package/dist/internals/parse-problem-details.d.ts.map +1 -0
- package/dist/internals/parse-problem-details.js +76 -0
- package/dist/internals/parse-problem-details.js.map +1 -0
- package/dist/internals/synthetic-details.d.ts +12 -0
- package/dist/internals/synthetic-details.d.ts.map +1 -0
- package/dist/internals/synthetic-details.js +19 -0
- package/dist/internals/synthetic-details.js.map +1 -0
- package/dist/verify/chains/bsc.d.ts +17 -0
- package/dist/verify/chains/bsc.d.ts.map +1 -0
- package/dist/verify/chains/bsc.js +15 -0
- package/dist/verify/chains/bsc.js.map +1 -0
- package/dist/verify/chains/btc.d.ts +22 -0
- package/dist/verify/chains/btc.d.ts.map +1 -0
- package/dist/verify/chains/btc.js +55 -0
- package/dist/verify/chains/btc.js.map +1 -0
- package/dist/verify/chains/cardano.d.ts +73 -0
- package/dist/verify/chains/cardano.d.ts.map +1 -0
- package/dist/verify/chains/cardano.js +175 -0
- package/dist/verify/chains/cardano.js.map +1 -0
- package/dist/verify/chains/evm.d.ts +21 -0
- package/dist/verify/chains/evm.d.ts.map +1 -0
- package/dist/verify/chains/evm.js +46 -0
- package/dist/verify/chains/evm.js.map +1 -0
- package/dist/verify/chains/polygon.d.ts +17 -0
- package/dist/verify/chains/polygon.d.ts.map +1 -0
- package/dist/verify/chains/polygon.js +15 -0
- package/dist/verify/chains/polygon.js.map +1 -0
- package/dist/verify/chains/secp256k1-bip32.d.ts +20 -0
- package/dist/verify/chains/secp256k1-bip32.d.ts.map +1 -0
- package/dist/verify/chains/secp256k1-bip32.js +88 -0
- package/dist/verify/chains/secp256k1-bip32.js.map +1 -0
- package/dist/verify/chains/ton-cell.d.ts +179 -0
- package/dist/verify/chains/ton-cell.d.ts.map +1 -0
- package/dist/verify/chains/ton-cell.js +614 -0
- package/dist/verify/chains/ton-cell.js.map +1 -0
- package/dist/verify/chains/ton.d.ts +84 -0
- package/dist/verify/chains/ton.d.ts.map +1 -0
- package/dist/verify/chains/ton.js +131 -0
- package/dist/verify/chains/ton.js.map +1 -0
- package/dist/verify/chains/tron.d.ts +21 -0
- package/dist/verify/chains/tron.d.ts.map +1 -0
- package/dist/verify/chains/tron.js +42 -0
- package/dist/verify/chains/tron.js.map +1 -0
- package/dist/verify/config.d.ts +41 -0
- package/dist/verify/config.d.ts.map +1 -0
- package/dist/verify/config.js +120 -0
- package/dist/verify/config.js.map +1 -0
- package/dist/verify/errors.d.ts +56 -0
- package/dist/verify/errors.d.ts.map +1 -0
- package/dist/verify/errors.js +58 -0
- package/dist/verify/errors.js.map +1 -0
- package/dist/verify/index.d.ts +119 -0
- package/dist/verify/index.d.ts.map +1 -0
- package/dist/verify/index.js +166 -0
- package/dist/verify/index.js.map +1 -0
- package/dist/verify/xpub-safety.d.ts +33 -0
- package/dist/verify/xpub-safety.d.ts.map +1 -0
- package/dist/verify/xpub-safety.js +54 -0
- package/dist/verify/xpub-safety.js.map +1 -0
- package/dist/verify-webhook-signature.d.ts +30 -0
- package/dist/verify-webhook-signature.d.ts.map +1 -0
- package/dist/verify-webhook-signature.js +84 -0
- package/dist/verify-webhook-signature.js.map +1 -0
- package/docs/00-getting-started.md +135 -0
- package/docs/01-authentication.md +114 -0
- package/docs/02-invoices.md +216 -0
- package/docs/03-rates-and-quotes.md +82 -0
- package/docs/04-webhooks.md +126 -0
- package/docs/05-errors.md +199 -0
- package/docs/05-sandbox.md +159 -0
- package/docs/06-idempotency.md +132 -0
- package/docs/examples/express-webhook-receiver.md +97 -0
- package/docs/examples/nextjs-route-handler.md +206 -0
- package/docs/examples/sandbox-vitest-suite.md +263 -0
- package/docs/index.md +66 -0
- package/docs/reference/client.md +392 -0
- package/docs/reference/errors.md +161 -0
- package/docs/reference/types.md +400 -0
- package/package.json +53 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TON address verification — operator-wallet StateInit reconstruction.
|
|
3
|
+
* Mirrors apps/web/lib/derivation/ton.ts byte-for-byte against the @ton/ton
|
|
4
|
+
* reference (parity test in ton.test.ts).
|
|
5
|
+
*
|
|
6
|
+
* Sources:
|
|
7
|
+
* - PRD FR96 / NFR-S11 (locally verify createInvoice address derives from xpub).
|
|
8
|
+
* - Architecture addendum + research §4.10.5.
|
|
9
|
+
* - TEP-0002 address layout, TON Cells representation-hash spec.
|
|
10
|
+
*
|
|
11
|
+
* @noTonRuntimeImport — verify/chains/ton.ts intentionally does NOT import
|
|
12
|
+
* `@ton/core` or `@ton/ton` at runtime; vendored cell builder lives in the
|
|
13
|
+
* sibling `ton-cell.ts`.
|
|
14
|
+
*/
|
|
15
|
+
import type { WalletEdgeKind } from '../../_shared/index.js';
|
|
16
|
+
/**
|
|
17
|
+
* Input shape for {@link verifyTonAddress}. Mirrors research §4.10.5 — the
|
|
18
|
+
* canonical 7-field tuple covering wallet version, subwallet id, workchain,
|
|
19
|
+
* project kind (production / testnet), and the API-returned address.
|
|
20
|
+
*
|
|
21
|
+
* `kind` controls only the testOnly bit on the user-friendly address
|
|
22
|
+
* encoding (`UQ`/`EQ` vs `kQ`/`0Q`). The TON wallet StateInit (and therefore
|
|
23
|
+
* the raw `<wc>:<hex>` address) is identical across kinds.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { verifyTonAddress, type VerifyTonInput } from '@txnod/sdk';
|
|
28
|
+
*
|
|
29
|
+
* const input: VerifyTonInput = {
|
|
30
|
+
* chain: 'ton',
|
|
31
|
+
* publicKeyHex: '0000000000000000000000000000000000000000000000000000000000000001',
|
|
32
|
+
* walletVersion: 'v3R2',
|
|
33
|
+
* subwalletId: 698_983_191,
|
|
34
|
+
* workchain: 0,
|
|
35
|
+
* kind: 'production',
|
|
36
|
+
* expectedAddress: 'UQBV4AQh9C66sbBNTwFMo96Tg7mtzmJZWDJzbRXxe8G8MXnb',
|
|
37
|
+
* };
|
|
38
|
+
* verifyTonAddress(input);
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export interface VerifyTonInput {
|
|
42
|
+
chain: 'ton';
|
|
43
|
+
publicKeyHex: string;
|
|
44
|
+
walletVersion: 'v3R2' | 'v4R2' | 'v5R1';
|
|
45
|
+
subwalletId: number;
|
|
46
|
+
workchain: 0 | -1;
|
|
47
|
+
kind: WalletEdgeKind;
|
|
48
|
+
expectedAddress: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Locally verify that a TON operator-wallet address derives from the
|
|
52
|
+
* `(walletVersion, subwalletId, publicKeyHex, workchain)` tuple. Throws
|
|
53
|
+
* `AddressVerificationError` on mismatch (raw `<wc>:<hex>` form is compared
|
|
54
|
+
* after parse, never the user-friendly string). Throws
|
|
55
|
+
* `TxnodInvalidXpubFormatError` for malformed `publicKeyHex` (length ≠ 64,
|
|
56
|
+
* non-hex), unknown `walletVersion`, or workchain ∉ `{0, -1}`.
|
|
57
|
+
*
|
|
58
|
+
* Returns void on success.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { verifyTonAddress, AddressVerificationError } from '@txnod/sdk';
|
|
63
|
+
*
|
|
64
|
+
* try {
|
|
65
|
+
* verifyTonAddress({
|
|
66
|
+
* chain: 'ton',
|
|
67
|
+
* publicKeyHex: '0000000000000000000000000000000000000000000000000000000000000001',
|
|
68
|
+
* walletVersion: 'v3R2',
|
|
69
|
+
* subwalletId: 698_983_191,
|
|
70
|
+
* workchain: 0,
|
|
71
|
+
* kind: 'production',
|
|
72
|
+
* expectedAddress: 'UQBV4AQh9C66sbBNTwFMo96Tg7mtzmJZWDJzbRXxe8G8MXnb',
|
|
73
|
+
* });
|
|
74
|
+
* } catch (err) {
|
|
75
|
+
* if (err instanceof AddressVerificationError) {
|
|
76
|
+
* console.error('ton verify failed', err.expected_address, err.derived_address);
|
|
77
|
+
* } else {
|
|
78
|
+
* throw err;
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function verifyTonAddress(input: VerifyTonInput): void;
|
|
84
|
+
//# sourceMappingURL=ton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ton.d.ts","sourceRoot":"","sources":["../../../src/verify/chains/ton.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAYpD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAClB,IAAI,EAAE,cAAc,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;CACzB;AAgED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CA4D5D"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { beginCell, computeWalletAddress, parseAddress, serializeAddressUserFriendly, } from './ton-cell.js';
|
|
2
|
+
import { TxnodInvalidXpubFormatError } from '../../errors.js';
|
|
3
|
+
import { syntheticDetails } from '../../internals/synthetic-details.js';
|
|
4
|
+
import { AddressVerificationError } from '../errors.js';
|
|
5
|
+
const DERIVATION_PATH_SENTINEL = 'ton-stateinit';
|
|
6
|
+
// Vendored wallet code BoCs — kept in lockstep with
|
|
7
|
+
// packages/shared/src/chains/ton-wallet-versions.ts (see Story 34.2). Drift is
|
|
8
|
+
// caught by the cell-parity test in ton.test.ts.
|
|
9
|
+
const WALLET_CODE_BOC = {
|
|
10
|
+
v3R2: 'b5ee9c724101010100710000deff0020dd2082014c97ba218201339cbab19f71b0ed44d0d31fd31f31d70bffe304e0a4f2608308d71820d31fd31fd31ff82313bbf263ed44d0d31fd31fd3ffd15132baf2a15144baf2a204f901541055f910f2a3f8009320d74a96d307d402fb00e8d101a4c8cb1fcb1fcbffc9ed5410bd6dad',
|
|
11
|
+
v4R2: 'b5ee9c72010214010002d4000114ff00f4a413f4bcf2c80b010201200203020148040504f8f28308d71820d31fd31fd31f02f823bbf264ed44d0d31fd31fd3fff404d15143baf2a15151baf2a205f901541064f910f2a3f80024a4c8cb1f5240cb1f5230cbff5210f400c9ed54f80f01d30721c0009f6c519320d74a96d307d402fb00e830e021c001e30021c002e30001c0039130e30d03a4c8cb1f12cb1fcbff1011121302e6d001d0d3032171b0925f04e022d749c120925f04e002d31f218210706c7567bd22821064737472bdb0925f05e003fa403020fa4401c8ca07cbffc9d0ed44d0810140d721f404305c810108f40a6fa131b3925f07e005d33fc8258210706c7567ba923830e30d03821064737472ba925f06e30d06070201200809007801fa00f40430f8276f2230500aa121bef2e0508210706c7567831eb17080185004cb0526cf1658fa0219f400cb6917cb1f5260cb3f20c98040fb0006008a5004810108f45930ed44d0810140d720c801cf16f400c9ed540172b08e23821064737472831eb17080185005cb055003cf1623fa0213cb6acb1fcb3fc98040fb00925f03e20201200a0b0059bd242b6f6a2684080a06b90fa0218470d4080847a4937d29910ce6903e9ff9837812801b7810148987159f31840201580c0d0011b8c97ed44d0d70b1f8003db29dfb513420405035c87d010c00b23281f2fff274006040423d029be84c600201200e0f0019adce76a26840206b90eb85ffc00019af1df6a26840106b90eb858fc0006ed207fa00d4d422f90005c8ca0715cbffc9d077748018c8cb05cb0222cf165005fa0214cb6b12ccccc973fb00c84014810108f451f2a7020070810108d718fa00d33fc8542047810108f451f2a782106e6f746570748018c8cb05cb025006cf165004fa0214cb6a12cb1fcb3fc973fb0002006c810108d718fa00d33f305224810108f459f2a782106473747270748018c8cb05cb025005cf165003fa0213cb6acb1f12cb3fc973fb00000af400c9ed54',
|
|
12
|
+
v5R1: 'b5ee9c7241021401000281000114ff00f4a413f4bcf2c80b01020120020d020148030402dcd020d749c120915b8f6320d70b1f2082106578746ebd21821073696e74bdb0925f03e082106578746eba8eb48020d72101d074d721fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d749810280b99130e070e2100f020120050c020120060902016e07080019adce76a2684020eb90eb85ffc00019af1df6a2684010eb90eb858fc00201480a0b0017b325fb51341c75c875c2c7e00011b262fb513435c280200019be5f0f6a2684080a0eb90fa02c0102f20e011e20d70b1f82107369676ebaf2e08a7f0f01e68ef0eda2edfb218308d722028308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ccf9109a28945f0adb31e1f2c087df02b35007b0f2d0845125baf2e0855036baf2e086f823bbf2d0882292f800de01a47fc8ca00cb1f01cf16c9ed542092f80fde70db3cd81003f6eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e09320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0ebd72c08142091709601d72c081c12e25210b1e30f20d74a111213009601fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e08c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed54007230d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9ed5493f2c08de20010935bdb31e1d74cd0b4d6c35e',
|
|
13
|
+
};
|
|
14
|
+
function pathError(message) {
|
|
15
|
+
return new TxnodInvalidXpubFormatError(syntheticDetails('invalid_xpub_format', 400, message));
|
|
16
|
+
}
|
|
17
|
+
function hex32(hex) {
|
|
18
|
+
if (hex.length !== 64 || !/^[0-9a-fA-F]+$/.test(hex)) {
|
|
19
|
+
throw pathError(`publicKeyHex must be 32 bytes (64 lowercase-hex chars), got '${hex}'.`);
|
|
20
|
+
}
|
|
21
|
+
const out = new Uint8Array(32);
|
|
22
|
+
for (let i = 0; i < 32; i++)
|
|
23
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
function buildDataCell(version, subwalletId, publicKeyHex) {
|
|
27
|
+
const pk = hex32(publicKeyHex);
|
|
28
|
+
switch (version) {
|
|
29
|
+
case 'v3R2':
|
|
30
|
+
return beginCell()
|
|
31
|
+
.storeUint(0, 32) // seqno
|
|
32
|
+
.storeUint(subwalletId, 32)
|
|
33
|
+
.storeBuffer(pk)
|
|
34
|
+
.endCell();
|
|
35
|
+
case 'v4R2':
|
|
36
|
+
return beginCell()
|
|
37
|
+
.storeUint(0, 32) // seqno
|
|
38
|
+
.storeUint(subwalletId, 32)
|
|
39
|
+
.storeBuffer(pk)
|
|
40
|
+
.storeBit(0) // empty plugins dict
|
|
41
|
+
.endCell();
|
|
42
|
+
case 'v5R1':
|
|
43
|
+
return beginCell()
|
|
44
|
+
.storeUint(1, 1) // signature auth allowed
|
|
45
|
+
.storeUint(0, 32) // seqno
|
|
46
|
+
.storeInt(subwalletId, 32) // walletId is signed int32 per W5 spec
|
|
47
|
+
.storeBuffer(pk)
|
|
48
|
+
.storeBit(0) // empty plugins dict
|
|
49
|
+
.endCell();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Locally verify that a TON operator-wallet address derives from the
|
|
54
|
+
* `(walletVersion, subwalletId, publicKeyHex, workchain)` tuple. Throws
|
|
55
|
+
* `AddressVerificationError` on mismatch (raw `<wc>:<hex>` form is compared
|
|
56
|
+
* after parse, never the user-friendly string). Throws
|
|
57
|
+
* `TxnodInvalidXpubFormatError` for malformed `publicKeyHex` (length ≠ 64,
|
|
58
|
+
* non-hex), unknown `walletVersion`, or workchain ∉ `{0, -1}`.
|
|
59
|
+
*
|
|
60
|
+
* Returns void on success.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { verifyTonAddress, AddressVerificationError } from '@txnod/sdk';
|
|
65
|
+
*
|
|
66
|
+
* try {
|
|
67
|
+
* verifyTonAddress({
|
|
68
|
+
* chain: 'ton',
|
|
69
|
+
* publicKeyHex: '0000000000000000000000000000000000000000000000000000000000000001',
|
|
70
|
+
* walletVersion: 'v3R2',
|
|
71
|
+
* subwalletId: 698_983_191,
|
|
72
|
+
* workchain: 0,
|
|
73
|
+
* kind: 'production',
|
|
74
|
+
* expectedAddress: 'UQBV4AQh9C66sbBNTwFMo96Tg7mtzmJZWDJzbRXxe8G8MXnb',
|
|
75
|
+
* });
|
|
76
|
+
* } catch (err) {
|
|
77
|
+
* if (err instanceof AddressVerificationError) {
|
|
78
|
+
* console.error('ton verify failed', err.expected_address, err.derived_address);
|
|
79
|
+
* } else {
|
|
80
|
+
* throw err;
|
|
81
|
+
* }
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function verifyTonAddress(input) {
|
|
86
|
+
if (input.workchain !== 0 && input.workchain !== -1) {
|
|
87
|
+
throw pathError(`workchain must be 0 or -1, got ${String(input.workchain)}.`);
|
|
88
|
+
}
|
|
89
|
+
if (!Object.hasOwn(WALLET_CODE_BOC, input.walletVersion)) {
|
|
90
|
+
throw pathError(`walletVersion '${String(input.walletVersion)}' is not supported (allowed: v3R2, v4R2, v5R1).`);
|
|
91
|
+
}
|
|
92
|
+
const codeBoCHex = WALLET_CODE_BOC[input.walletVersion];
|
|
93
|
+
if (!Number.isInteger(input.subwalletId) ||
|
|
94
|
+
input.subwalletId < -0x80000000 ||
|
|
95
|
+
input.subwalletId > 0xffffffff) {
|
|
96
|
+
throw pathError(`subwalletId must be a 32-bit integer, got ${String(input.subwalletId)}.`);
|
|
97
|
+
}
|
|
98
|
+
const parts = computeWalletAddress({
|
|
99
|
+
codeBoCHex,
|
|
100
|
+
subwalletId: input.subwalletId,
|
|
101
|
+
publicKeyHex: input.publicKeyHex,
|
|
102
|
+
workchain: input.workchain,
|
|
103
|
+
dataCellBuilder: ({ subwalletId, publicKeyHex }) => buildDataCell(input.walletVersion, subwalletId, publicKeyHex),
|
|
104
|
+
});
|
|
105
|
+
let expectedParts;
|
|
106
|
+
try {
|
|
107
|
+
expectedParts = parseAddress(input.expectedAddress);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
throw pathError(`expectedAddress '${input.expectedAddress}' is not a valid TON address.`);
|
|
111
|
+
}
|
|
112
|
+
const isTestnet = input.kind === 'testnet';
|
|
113
|
+
const derivedAddress = serializeAddressUserFriendly(parts, {
|
|
114
|
+
bounceable: false,
|
|
115
|
+
testOnly: isTestnet,
|
|
116
|
+
});
|
|
117
|
+
// Raw `<wc>:<hex>` form carries no testOnly bit, so we only enforce the
|
|
118
|
+
// kind flag when the partner supplied a user-friendly EQ/UQ/kQ/0Q form.
|
|
119
|
+
const isUserFriendly = !input.expectedAddress.includes(':');
|
|
120
|
+
if (expectedParts.workchain !== parts.workchain ||
|
|
121
|
+
expectedParts.hex !== parts.hex ||
|
|
122
|
+
(isUserFriendly && expectedParts.testOnly !== isTestnet)) {
|
|
123
|
+
throw new AddressVerificationError({
|
|
124
|
+
chain: 'ton',
|
|
125
|
+
derivation_path: DERIVATION_PATH_SENTINEL,
|
|
126
|
+
expected_address: input.expectedAddress,
|
|
127
|
+
derived_address: derivedAddress,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=ton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ton.js","sourceRoot":"","sources":["../../../src/verify/chains/ton.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRON verification — BIP-44 coin_type=195'.
|
|
3
|
+
* Cross-checked byte-for-byte against server-side derivation in
|
|
4
|
+
* apps/web/lib/derivation/tron.ts (which uses the same allowlisted stack —
|
|
5
|
+
* near-verbatim port; Story 25.3 AC 9 fixture vectors).
|
|
6
|
+
*
|
|
7
|
+
* Sources:
|
|
8
|
+
* - PRD FR96: SDK locally verifies createInvoice address derives from xpub.
|
|
9
|
+
* - Architecture addendum §4.1, §4.3, AD-7.
|
|
10
|
+
* - Discovery §C.6, §E.1.
|
|
11
|
+
* - SLIP-0044 coin_type=195 (https://github.com/satoshilabs/slips/blob/master/slip-0044.md).
|
|
12
|
+
* - TRON address spec (TRC-102).
|
|
13
|
+
*/
|
|
14
|
+
export interface VerifyTronInput {
|
|
15
|
+
xpub: string;
|
|
16
|
+
derivation_path: string;
|
|
17
|
+
expected_address: string;
|
|
18
|
+
}
|
|
19
|
+
/** Throws AddressVerificationError on mismatch; returns void on success. */
|
|
20
|
+
export declare function verifyTron(input: VerifyTronInput): void;
|
|
21
|
+
//# sourceMappingURL=tron.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tron.d.ts","sourceRoot":"","sources":["../../../src/verify/chains/tron.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAWH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAoBvD"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRON verification — BIP-44 coin_type=195'.
|
|
3
|
+
* Cross-checked byte-for-byte against server-side derivation in
|
|
4
|
+
* apps/web/lib/derivation/tron.ts (which uses the same allowlisted stack —
|
|
5
|
+
* near-verbatim port; Story 25.3 AC 9 fixture vectors).
|
|
6
|
+
*
|
|
7
|
+
* Sources:
|
|
8
|
+
* - PRD FR96: SDK locally verifies createInvoice address derives from xpub.
|
|
9
|
+
* - Architecture addendum §4.1, §4.3, AD-7.
|
|
10
|
+
* - Discovery §C.6, §E.1.
|
|
11
|
+
* - SLIP-0044 coin_type=195 (https://github.com/satoshilabs/slips/blob/master/slip-0044.md).
|
|
12
|
+
* - TRON address spec (TRC-102).
|
|
13
|
+
*/
|
|
14
|
+
import { secp256k1 } from '@noble/curves/secp256k1.js';
|
|
15
|
+
import { keccak_256 } from '@noble/hashes/sha3.js';
|
|
16
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
17
|
+
import { createBase58check } from '@scure/base';
|
|
18
|
+
import { deriveSecp256k1ChildPubkey } from './secp256k1-bip32.js';
|
|
19
|
+
import { AddressVerificationError } from '../errors.js';
|
|
20
|
+
const b58check = createBase58check(sha256);
|
|
21
|
+
/** Throws AddressVerificationError on mismatch; returns void on success. */
|
|
22
|
+
export function verifyTron(input) {
|
|
23
|
+
const pubkey = deriveSecp256k1ChildPubkey({
|
|
24
|
+
xpub: input.xpub,
|
|
25
|
+
derivation_path: input.derivation_path,
|
|
26
|
+
});
|
|
27
|
+
const uncompressed = secp256k1.Point.fromBytes(pubkey).toBytes(false);
|
|
28
|
+
const hash = keccak_256(uncompressed.slice(1));
|
|
29
|
+
const addressBytes = new Uint8Array(21);
|
|
30
|
+
addressBytes[0] = 0x41;
|
|
31
|
+
addressBytes.set(hash.slice(-20), 1);
|
|
32
|
+
const derived = b58check.encode(addressBytes);
|
|
33
|
+
if (derived !== input.expected_address) {
|
|
34
|
+
throw new AddressVerificationError({
|
|
35
|
+
chain: 'tron',
|
|
36
|
+
derivation_path: input.derivation_path,
|
|
37
|
+
expected_address: input.expected_address,
|
|
38
|
+
derived_address: derived,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=tron.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tron.js","sourceRoot":"","sources":["../../../src/verify/chains/tron.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAQ3C,4EAA4E;AAC5E,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,MAAM,MAAM,GAAG,0BAA0B,CAAC;QACxC,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,eAAe,EAAE,KAAK,CAAC,eAAe;KACvC,CAAC,CAAC;IACH,MAAM,YAAY,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACtE,MAAM,IAAI,GAAG,UAAU,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,MAAM,YAAY,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IACxC,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACvB,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAE9C,IAAI,OAAO,KAAK,KAAK,CAAC,gBAAgB,EAAE,CAAC;QACvC,MAAM,IAAI,wBAAwB,CAAC;YACjC,KAAK,EAAE,MAAM;YACb,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;YACxC,eAAe,EAAE,OAAO;SACzB,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses TXNOD_*_XPUB env vars into a per-chain xpub-array record, plus the
|
|
3
|
+
* TON-specific tuple consumed by `verifyTonAddress`. Pure, sync, env-arg
|
|
4
|
+
* defaultable so tests can pass fixture records directly.
|
|
5
|
+
*/
|
|
6
|
+
import type { Chain } from '../_shared/index.js';
|
|
7
|
+
declare const SUPPORTED_TON_WALLET_VERSIONS: readonly ["v3R2", "v4R2", "v5R1"];
|
|
8
|
+
type TonWalletVersion = (typeof SUPPORTED_TON_WALLET_VERSIONS)[number];
|
|
9
|
+
/** Resolved TON-specific config — all fields required, with sensible defaults. */
|
|
10
|
+
export interface TonXpubConfig {
|
|
11
|
+
publicKeyHex: string;
|
|
12
|
+
walletVersion: TonWalletVersion;
|
|
13
|
+
subwalletId: number;
|
|
14
|
+
workchain: 0 | -1;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse the partner's TXNOD_*_XPUB env vars into a per-chain record of xpub
|
|
18
|
+
* arrays. Each value may be a single xpub, a comma-separated newest-first
|
|
19
|
+
* list, or a JSON array. Missing or empty entries become `[]`.
|
|
20
|
+
*
|
|
21
|
+
* TON has no xpub/derivation: see {@link parseTonConfig}.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseXpubConfig(env?: NodeJS.ProcessEnv): Record<Chain, string[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Parse the partner's TON-specific env vars into a {@link TonXpubConfig}.
|
|
26
|
+
* Returns `undefined` if `TXNOD_TON_PUBKEY` is unset — automatic TON address
|
|
27
|
+
* verification is then disabled (parity with omitting `TXNOD_<chain>_XPUB`
|
|
28
|
+
* for the HD chains).
|
|
29
|
+
*
|
|
30
|
+
* Required env: `TXNOD_TON_PUBKEY` — 64-char lowercase-hex of the operator's
|
|
31
|
+
* 32-byte Ed25519 public key (e.g. exported from a Ledger device or a TON
|
|
32
|
+
* wallet "Show recovery key" screen — never the secret key).
|
|
33
|
+
*
|
|
34
|
+
* Optional env (defaults match the most common Ledger TON setup):
|
|
35
|
+
* - `TXNOD_TON_WALLET_VERSION` — 'v3R2' | 'v4R2' | 'v5R1' (default `v4R2`)
|
|
36
|
+
* - `TXNOD_TON_SUBWALLET_ID` — int32 (default 698_983_191)
|
|
37
|
+
* - `TXNOD_TON_WORKCHAIN` — 0 | -1 (default 0; basechain)
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseTonConfig(env?: NodeJS.ProcessEnv): TonXpubConfig | undefined;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/verify/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAI3C,QAAA,MAAM,6BAA6B,mCAAoC,CAAC;AACxE,KAAK,gBAAgB,GAAG,CAAC,OAAO,6BAA6B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,gBAAgB,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACnB;AA8CD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAazB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,aAAa,GAAG,SAAS,CAkD3B"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses TXNOD_*_XPUB env vars into a per-chain xpub-array record, plus the
|
|
3
|
+
* TON-specific tuple consumed by `verifyTonAddress`. Pure, sync, env-arg
|
|
4
|
+
* defaultable so tests can pass fixture records directly.
|
|
5
|
+
*/
|
|
6
|
+
const SUPPORTED_CHAINS = ['btc', 'eth', 'tron', 'ada', 'polygon', 'bsc'];
|
|
7
|
+
const SUPPORTED_TON_WALLET_VERSIONS = ['v3R2', 'v4R2', 'v5R1'];
|
|
8
|
+
const DEFAULT_TON_WALLET_VERSION = 'v4R2';
|
|
9
|
+
const DEFAULT_TON_SUBWALLET_ID = 698_983_191; // standard wallet_id for v3R2/v4R2
|
|
10
|
+
const DEFAULT_TON_WORKCHAIN = 0;
|
|
11
|
+
const ENV_VAR_TO_CHAIN = [
|
|
12
|
+
['TXNOD_BTC_XPUB', 'btc'],
|
|
13
|
+
['TXNOD_ETH_XPUB', 'eth'],
|
|
14
|
+
['TXNOD_TRON_XPUB', 'tron'],
|
|
15
|
+
['TXNOD_CARDANO_ACCOUNT_PUBKEY', 'ada'],
|
|
16
|
+
['TXNOD_POLYGON_XPUB', 'polygon'],
|
|
17
|
+
['TXNOD_BNB_XPUB', 'bsc'],
|
|
18
|
+
];
|
|
19
|
+
function parseValue(raw) {
|
|
20
|
+
// Disambiguation precedence: '[' → JSON array, ',' → CSV newest-first, else single.
|
|
21
|
+
const trimmedStart = raw.trimStart();
|
|
22
|
+
if (trimmedStart.startsWith('[')) {
|
|
23
|
+
const parsed = JSON.parse(trimmedStart);
|
|
24
|
+
if (!Array.isArray(parsed)) {
|
|
25
|
+
throw new TypeError(`xpub env var JSON value must be an array of strings; got ${typeof parsed}.`);
|
|
26
|
+
}
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const el of parsed) {
|
|
29
|
+
if (typeof el !== 'string') {
|
|
30
|
+
throw new TypeError(`xpub env var JSON array element must be a string; got ${typeof el}.`);
|
|
31
|
+
}
|
|
32
|
+
const trimmed = el.trim();
|
|
33
|
+
if (trimmed.length > 0)
|
|
34
|
+
out.push(trimmed);
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
if (raw.includes(',')) {
|
|
39
|
+
return raw
|
|
40
|
+
.split(',')
|
|
41
|
+
.map((s) => s.trim())
|
|
42
|
+
.filter((s) => s.length > 0);
|
|
43
|
+
}
|
|
44
|
+
return [raw.trim()];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse the partner's TXNOD_*_XPUB env vars into a per-chain record of xpub
|
|
48
|
+
* arrays. Each value may be a single xpub, a comma-separated newest-first
|
|
49
|
+
* list, or a JSON array. Missing or empty entries become `[]`.
|
|
50
|
+
*
|
|
51
|
+
* TON has no xpub/derivation: see {@link parseTonConfig}.
|
|
52
|
+
*/
|
|
53
|
+
export function parseXpubConfig(env = process.env) {
|
|
54
|
+
const out = Object.fromEntries(SUPPORTED_CHAINS.map((c) => [c, []]));
|
|
55
|
+
out['ton'] = []; // TON is verified via parseTonConfig, not xpubs
|
|
56
|
+
for (const [envVar, chain] of ENV_VAR_TO_CHAIN) {
|
|
57
|
+
const raw = env[envVar];
|
|
58
|
+
if (raw === undefined)
|
|
59
|
+
continue;
|
|
60
|
+
const trimmed = raw.trim();
|
|
61
|
+
if (trimmed.length === 0)
|
|
62
|
+
continue;
|
|
63
|
+
out[chain] = parseValue(raw);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Parse the partner's TON-specific env vars into a {@link TonXpubConfig}.
|
|
69
|
+
* Returns `undefined` if `TXNOD_TON_PUBKEY` is unset — automatic TON address
|
|
70
|
+
* verification is then disabled (parity with omitting `TXNOD_<chain>_XPUB`
|
|
71
|
+
* for the HD chains).
|
|
72
|
+
*
|
|
73
|
+
* Required env: `TXNOD_TON_PUBKEY` — 64-char lowercase-hex of the operator's
|
|
74
|
+
* 32-byte Ed25519 public key (e.g. exported from a Ledger device or a TON
|
|
75
|
+
* wallet "Show recovery key" screen — never the secret key).
|
|
76
|
+
*
|
|
77
|
+
* Optional env (defaults match the most common Ledger TON setup):
|
|
78
|
+
* - `TXNOD_TON_WALLET_VERSION` — 'v3R2' | 'v4R2' | 'v5R1' (default `v4R2`)
|
|
79
|
+
* - `TXNOD_TON_SUBWALLET_ID` — int32 (default 698_983_191)
|
|
80
|
+
* - `TXNOD_TON_WORKCHAIN` — 0 | -1 (default 0; basechain)
|
|
81
|
+
*/
|
|
82
|
+
export function parseTonConfig(env = process.env) {
|
|
83
|
+
const rawPk = env['TXNOD_TON_PUBKEY']?.trim();
|
|
84
|
+
if (rawPk === undefined || rawPk.length === 0)
|
|
85
|
+
return undefined;
|
|
86
|
+
const pk = rawPk.toLowerCase();
|
|
87
|
+
if (pk.length !== 64 || !/^[0-9a-f]{64}$/.test(pk)) {
|
|
88
|
+
throw new TypeError(`TXNOD_TON_PUBKEY must be 64 lowercase-hex chars (32-byte Ed25519 pubkey); got '${rawPk}'.`);
|
|
89
|
+
}
|
|
90
|
+
const versionRaw = env['TXNOD_TON_WALLET_VERSION']?.trim() ?? DEFAULT_TON_WALLET_VERSION;
|
|
91
|
+
if (!SUPPORTED_TON_WALLET_VERSIONS.includes(versionRaw)) {
|
|
92
|
+
throw new TypeError(`TXNOD_TON_WALLET_VERSION must be one of ${SUPPORTED_TON_WALLET_VERSIONS.join(', ')}; got '${versionRaw}'.`);
|
|
93
|
+
}
|
|
94
|
+
const walletVersion = versionRaw;
|
|
95
|
+
let subwalletId = DEFAULT_TON_SUBWALLET_ID;
|
|
96
|
+
const rawSwid = env['TXNOD_TON_SUBWALLET_ID']?.trim();
|
|
97
|
+
if (rawSwid !== undefined && rawSwid.length > 0) {
|
|
98
|
+
if (!/^-?\d+$/.test(rawSwid)) {
|
|
99
|
+
throw new TypeError(`TXNOD_TON_SUBWALLET_ID must be an integer; got '${rawSwid}'.`);
|
|
100
|
+
}
|
|
101
|
+
const n = Number(rawSwid);
|
|
102
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n < -0x80000000 || n > 0xffffffff) {
|
|
103
|
+
throw new TypeError(`TXNOD_TON_SUBWALLET_ID out of int32 range; got ${rawSwid}.`);
|
|
104
|
+
}
|
|
105
|
+
subwalletId = n;
|
|
106
|
+
}
|
|
107
|
+
let workchain = DEFAULT_TON_WORKCHAIN;
|
|
108
|
+
const rawWc = env['TXNOD_TON_WORKCHAIN']?.trim();
|
|
109
|
+
if (rawWc !== undefined && rawWc.length > 0) {
|
|
110
|
+
if (rawWc === '0')
|
|
111
|
+
workchain = 0;
|
|
112
|
+
else if (rawWc === '-1')
|
|
113
|
+
workchain = -1;
|
|
114
|
+
else {
|
|
115
|
+
throw new TypeError(`TXNOD_TON_WORKCHAIN must be '0' or '-1'; got '${rawWc}'.`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { publicKeyHex: pk, walletVersion, subwalletId, workchain };
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/verify/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { TxnodError } from '../errors.js';
|
|
2
|
+
import type { Chain } from '../_shared/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Thrown by chain-level verify functions (verifyBtc, verifyEvm, verifyTron,
|
|
5
|
+
* verifyPolygon, verifyBsc) when the address derived from the operator's
|
|
6
|
+
* registered xpub at the given derivation_path does NOT match the address
|
|
7
|
+
* returned by the API. The orchestrator (Story 25.5) re-throws after
|
|
8
|
+
* iterating all xpubs in the multi-xpub list (AD-10).
|
|
9
|
+
*
|
|
10
|
+
* Sources:
|
|
11
|
+
* - PRD FR96: SDK locally verifies createInvoice address derives from xpub.
|
|
12
|
+
* - Architecture addendum §4.3 (public API + edge cases (a)(b)).
|
|
13
|
+
* - Discovery §E.2 (Cardano stake-substitution defense informs why no
|
|
14
|
+
* stake-only compare is permitted; for BTC/EVM/TRON the address fully
|
|
15
|
+
* determines the funds destination, so a strict-equality compare suffices).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { TxnodClient } from '@txnod/sdk';
|
|
20
|
+
*
|
|
21
|
+
* const client = new TxnodClient({
|
|
22
|
+
* projectId: process.env.TXNOD_PROJECT_ID!,
|
|
23
|
+
* apiSecret: process.env.TXNOD_API_SECRET!,
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* try {
|
|
27
|
+
* const invoice = await client.createInvoice({
|
|
28
|
+
* amount_usd: 10.0,
|
|
29
|
+
* coin: 'btc',
|
|
30
|
+
* external_id: 'order-123',
|
|
31
|
+
* callback_url: 'https://my-site.com/webhooks/txnod',
|
|
32
|
+
* });
|
|
33
|
+
* console.log(invoice.address);
|
|
34
|
+
* } catch (err) {
|
|
35
|
+
* if (err instanceof AddressVerificationError) {
|
|
36
|
+
* console.error('Address verify failed', err.chain, err.expected_address, err.derived_address);
|
|
37
|
+
* } else {
|
|
38
|
+
* throw err;
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare class AddressVerificationError extends TxnodError {
|
|
44
|
+
readonly kind: "address_verification";
|
|
45
|
+
readonly chain: Chain;
|
|
46
|
+
readonly derivation_path: string;
|
|
47
|
+
readonly expected_address: string;
|
|
48
|
+
readonly derived_address: string;
|
|
49
|
+
constructor(input: {
|
|
50
|
+
chain: Chain;
|
|
51
|
+
derivation_path: string;
|
|
52
|
+
expected_address: string;
|
|
53
|
+
derived_address: string;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/verify/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,qBAAa,wBAAyB,SAAQ,UAAU;IACtD,SAAkB,IAAI,EAAG,sBAAsB,CAAU;IACzD,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;gBAErB,KAAK,EAAE;QACjB,KAAK,EAAE,KAAK,CAAC;QACb,eAAe,EAAE,MAAM,CAAC;QACxB,gBAAgB,EAAE,MAAM,CAAC;QACzB,eAAe,EAAE,MAAM,CAAC;KACzB;CAcF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { TxnodError } from '../errors.js';
|
|
2
|
+
import { syntheticDetails } from '../internals/synthetic-details.js';
|
|
3
|
+
/**
|
|
4
|
+
* Thrown by chain-level verify functions (verifyBtc, verifyEvm, verifyTron,
|
|
5
|
+
* verifyPolygon, verifyBsc) when the address derived from the operator's
|
|
6
|
+
* registered xpub at the given derivation_path does NOT match the address
|
|
7
|
+
* returned by the API. The orchestrator (Story 25.5) re-throws after
|
|
8
|
+
* iterating all xpubs in the multi-xpub list (AD-10).
|
|
9
|
+
*
|
|
10
|
+
* Sources:
|
|
11
|
+
* - PRD FR96: SDK locally verifies createInvoice address derives from xpub.
|
|
12
|
+
* - Architecture addendum §4.3 (public API + edge cases (a)(b)).
|
|
13
|
+
* - Discovery §E.2 (Cardano stake-substitution defense informs why no
|
|
14
|
+
* stake-only compare is permitted; for BTC/EVM/TRON the address fully
|
|
15
|
+
* determines the funds destination, so a strict-equality compare suffices).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { TxnodClient } from '@txnod/sdk';
|
|
20
|
+
*
|
|
21
|
+
* const client = new TxnodClient({
|
|
22
|
+
* projectId: process.env.TXNOD_PROJECT_ID!,
|
|
23
|
+
* apiSecret: process.env.TXNOD_API_SECRET!,
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* try {
|
|
27
|
+
* const invoice = await client.createInvoice({
|
|
28
|
+
* amount_usd: 10.0,
|
|
29
|
+
* coin: 'btc',
|
|
30
|
+
* external_id: 'order-123',
|
|
31
|
+
* callback_url: 'https://my-site.com/webhooks/txnod',
|
|
32
|
+
* });
|
|
33
|
+
* console.log(invoice.address);
|
|
34
|
+
* } catch (err) {
|
|
35
|
+
* if (err instanceof AddressVerificationError) {
|
|
36
|
+
* console.error('Address verify failed', err.chain, err.expected_address, err.derived_address);
|
|
37
|
+
* } else {
|
|
38
|
+
* throw err;
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export class AddressVerificationError extends TxnodError {
|
|
44
|
+
kind = 'address_verification';
|
|
45
|
+
chain;
|
|
46
|
+
derivation_path;
|
|
47
|
+
expected_address;
|
|
48
|
+
derived_address;
|
|
49
|
+
constructor(input) {
|
|
50
|
+
super(syntheticDetails('auth_invalid', 400, `Address verification failed for chain '${input.chain}': expected '${input.expected_address}', derived '${input.derived_address}' from path '${input.derivation_path}'.`));
|
|
51
|
+
this.name = 'AddressVerificationError';
|
|
52
|
+
this.chain = input.chain;
|
|
53
|
+
this.derivation_path = input.derivation_path;
|
|
54
|
+
this.expected_address = input.expected_address;
|
|
55
|
+
this.derived_address = input.derived_address;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/verify/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAGrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,OAAO,wBAAyB,SAAQ,UAAU;IACpC,IAAI,GAAG,sBAA+B,CAAC;IAChD,KAAK,CAAQ;IACb,eAAe,CAAS;IACxB,gBAAgB,CAAS;IACzB,eAAe,CAAS;IAEjC,YAAY,KAKX;QACC,KAAK,CACH,gBAAgB,CACd,cAAc,EACd,GAAG,EACH,0CAA0C,KAAK,CAAC,KAAK,gBAAgB,KAAK,CAAC,gBAAgB,eAAe,KAAK,CAAC,eAAe,gBAAgB,KAAK,CAAC,eAAe,IAAI,CACzK,CACF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC,eAAe,CAAC;QAC7C,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC,gBAAgB,CAAC;QAC/C,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC,eAAe,CAAC;IAC/C,CAAC;CACF"}
|