@pushchain/core 6.0.19 → 6.0.21

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 (112) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/package.json +1 -1
  3. package/src/lib/cache/cache.js +2 -0
  4. package/src/lib/constants/abi/index.d.ts +1 -0
  5. package/src/lib/constants/abi/index.js +8 -1
  6. package/src/lib/constants/abi/pc20.evm.d.ts +217 -0
  7. package/src/lib/constants/abi/pc20.evm.js +150 -0
  8. package/src/lib/constants/abi/prc20.evm.d.ts +172 -0
  9. package/src/lib/constants/abi/prc20.evm.js +97 -0
  10. package/src/lib/constants/abi/universalGatewayV0.json +1 -1
  11. package/src/lib/constants/chain.js +38 -8
  12. package/src/lib/constants/selectors.js +2 -2
  13. package/src/lib/constants/tokens.js +4 -3
  14. package/src/lib/formatters.js +16 -6
  15. package/src/lib/generated/uexecutor/v1/query.js +2 -4
  16. package/src/lib/generated/uexecutor/v1/types.d.ts +14 -0
  17. package/src/lib/generated/uexecutor/v1/types.js +90 -47
  18. package/src/lib/generated/uexecutor/v2/query.js +2 -4
  19. package/src/lib/generated/uexecutor/v2/types.d.ts +20 -0
  20. package/src/lib/generated/uexecutor/v2/types.js +49 -33
  21. package/src/lib/generated/v1/tx.js +30 -37
  22. package/src/lib/index.d.ts +4 -1
  23. package/src/lib/index.js +21 -3
  24. package/src/lib/internal/buffer.js +1 -2
  25. package/src/lib/orchestrator/cea-utils.js +73 -83
  26. package/src/lib/orchestrator/internals/account-manager.js +130 -139
  27. package/src/lib/orchestrator/internals/cascade.d.ts +0 -6
  28. package/src/lib/orchestrator/internals/cascade.js +1184 -1059
  29. package/src/lib/orchestrator/internals/confirmation.js +138 -148
  30. package/src/lib/orchestrator/internals/error-decoder.js +1 -1
  31. package/src/lib/orchestrator/internals/errors.js +12 -3
  32. package/src/lib/orchestrator/internals/execute-funds-only.d.ts +2 -2
  33. package/src/lib/orchestrator/internals/execute-funds-only.js +297 -259
  34. package/src/lib/orchestrator/internals/execute-funds-payload.d.ts +2 -2
  35. package/src/lib/orchestrator/internals/execute-funds-payload.js +220 -220
  36. package/src/lib/orchestrator/internals/execute-standard.d.ts +0 -8
  37. package/src/lib/orchestrator/internals/execute-standard.js +339 -342
  38. package/src/lib/orchestrator/internals/gas-calculator.js +497 -520
  39. package/src/lib/orchestrator/internals/gas-usd-sizer.js +101 -110
  40. package/src/lib/orchestrator/internals/gateway-client.js +102 -103
  41. package/src/lib/orchestrator/internals/helpers.js +30 -27
  42. package/src/lib/orchestrator/internals/inbound-tracker.js +234 -238
  43. package/src/lib/orchestrator/internals/outbound-sync.js +463 -452
  44. package/src/lib/orchestrator/internals/outbound-tracker.js +27 -34
  45. package/src/lib/orchestrator/internals/payload-builder.d.ts +2 -2
  46. package/src/lib/orchestrator/internals/payload-builder.js +119 -100
  47. package/src/lib/orchestrator/internals/pc-usd-oracle.js +121 -129
  48. package/src/lib/orchestrator/internals/pc20/address-codec.d.ts +31 -0
  49. package/src/lib/orchestrator/internals/pc20/address-codec.js +124 -0
  50. package/src/lib/orchestrator/internals/pc20/chain-namespace.d.ts +68 -0
  51. package/src/lib/orchestrator/internals/pc20/chain-namespace.js +136 -0
  52. package/src/lib/orchestrator/internals/pc20/errors.d.ts +160 -0
  53. package/src/lib/orchestrator/internals/pc20/errors.js +236 -0
  54. package/src/lib/orchestrator/internals/pc20/export.d.ts +172 -0
  55. package/src/lib/orchestrator/internals/pc20/export.js +412 -0
  56. package/src/lib/orchestrator/internals/pc20/gate.d.ts +67 -0
  57. package/src/lib/orchestrator/internals/pc20/gate.js +184 -0
  58. package/src/lib/orchestrator/internals/pc20/resolver.d.ts +187 -0
  59. package/src/lib/orchestrator/internals/pc20/resolver.js +658 -0
  60. package/src/lib/orchestrator/internals/pc20/svm.d.ts +189 -0
  61. package/src/lib/orchestrator/internals/pc20/svm.js +286 -0
  62. package/src/lib/orchestrator/internals/pc20/tracking.d.ts +46 -0
  63. package/src/lib/orchestrator/internals/pc20/tracking.js +109 -0
  64. package/src/lib/orchestrator/internals/preflight.js +1 -1
  65. package/src/lib/orchestrator/internals/progress-route-hooks.js +2 -2
  66. package/src/lib/orchestrator/internals/push-chain-tx.js +319 -331
  67. package/src/lib/orchestrator/internals/quote.js +90 -94
  68. package/src/lib/orchestrator/internals/rescue.js +76 -79
  69. package/src/lib/orchestrator/internals/response-builder.d.ts +28 -1
  70. package/src/lib/orchestrator/internals/response-builder.js +817 -701
  71. package/src/lib/orchestrator/internals/route-handlers.d.ts +7 -15
  72. package/src/lib/orchestrator/internals/route-handlers.js +1512 -1178
  73. package/src/lib/orchestrator/internals/signing.js +78 -83
  74. package/src/lib/orchestrator/internals/svm-bridge.d.ts +2 -2
  75. package/src/lib/orchestrator/internals/svm-bridge.js +161 -93
  76. package/src/lib/orchestrator/internals/svm-helpers.d.ts +15 -1
  77. package/src/lib/orchestrator/internals/svm-helpers.js +68 -24
  78. package/src/lib/orchestrator/internals/svm-rent.d.ts +19 -1
  79. package/src/lib/orchestrator/internals/svm-rent.js +163 -141
  80. package/src/lib/orchestrator/internals/tx-transformer.js +26 -23
  81. package/src/lib/orchestrator/internals/uea-manager.js +89 -86
  82. package/src/lib/orchestrator/max-pc-for-gas.js +5 -6
  83. package/src/lib/orchestrator/orchestrator.js +217 -200
  84. package/src/lib/orchestrator/orchestrator.types.d.ts +121 -1
  85. package/src/lib/orchestrator/orchestrator.types.js +19 -0
  86. package/src/lib/orchestrator/payload-builders.d.ts +2 -2
  87. package/src/lib/orchestrator/payload-builders.js +26 -24
  88. package/src/lib/orchestrator/route-detector.js +26 -9
  89. package/src/lib/orchestrator/svm-idl/ix-encoder.js +1 -2
  90. package/src/lib/orchestrator/svm-idl/normalize-address.js +2 -2
  91. package/src/lib/orchestrator/svm-idl/resolve.js +1 -2
  92. package/src/lib/price-fetch/price-fetch.js +67 -68
  93. package/src/lib/progress-hook/progress-hook.js +124 -72
  94. package/src/lib/push-chain/push-chain.js +171 -153
  95. package/src/lib/push-client/push-client.js +223 -237
  96. package/src/lib/universal/account/account.js +349 -365
  97. package/src/lib/universal/signer/signer.d.ts +2 -1
  98. package/src/lib/universal/signer/signer.js +315 -299
  99. package/src/lib/universal-tx-detector/auto-detect.js +55 -60
  100. package/src/lib/universal-tx-detector/cascade.js +160 -172
  101. package/src/lib/universal-tx-detector/child-inbounds.js +67 -75
  102. package/src/lib/universal-tx-detector/classify.js +2 -3
  103. package/src/lib/universal-tx-detector/detector-svm.js +139 -147
  104. package/src/lib/universal-tx-detector/detector.js +145 -157
  105. package/src/lib/universal-tx-detector/normalize.js +7 -7
  106. package/src/lib/utils/external-tx-hash.js +1 -2
  107. package/src/lib/utils.d.ts +44 -0
  108. package/src/lib/utils.js +762 -683
  109. package/src/lib/vm-client/evm-client.js +186 -202
  110. package/src/lib/vm-client/svm-client.d.ts +1 -1
  111. package/src/lib/vm-client/svm-client.js +220 -224
  112. package/src/lib/vm-client/vm-client.types.d.ts +14 -2
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Solana PC20 support.
3
+ *
4
+ * Derived directly from the program source at
5
+ * `push-chain-gateway-contracts@pc20-3rd-iteration`:
6
+ * `contracts/svm-gateway/programs/universal-gateway/src/state.rs`
7
+ * `contracts/svm-gateway/programs/universal-gateway/src/instructions/pc20.rs`
8
+ *
9
+ * No built IDL artifact exists in that repo yet, so the PDA seeds and the
10
+ * `Pc20State` layout are mirrored here rather than read from an IDL. Both are
11
+ * pinned by unit test against the constants below; if the program changes them,
12
+ * the tests must be updated deliberately rather than silently drifting.
13
+ *
14
+ * Relevant program facts, and why each matters to the SDK:
15
+ *
16
+ * - `pc20_mint = PDA([b"pc20_mint", source_asset_20], program_id)` where
17
+ * `source_asset` is the **20-byte** Push-native address, not a 32-byte
18
+ * padded one. Getting this wrong yields a valid-looking but wrong mint.
19
+ * - `pc20_state = PDA([b"pc20_state", pc20_mint], program_id)`.
20
+ * - An inbound burn passes `remaining_accounts = [pc20_state, pc20_mint]`, in
21
+ * that order (`pc20.rs:244-245` reads index 0 then 1).
22
+ * - An inbound burn passes NO gateway token vault — the wrapper is burned,
23
+ * not escrowed.
24
+ */
25
+ import { PublicKey } from '@solana/web3.js';
26
+ import { Buffer } from 'buffer';
27
+ /** `state.rs:13` — `pub const PC20_MINT_SEED: &[u8] = b"pc20_mint";` */
28
+ export declare const PC20_MINT_SEED: Buffer;
29
+ /** `state.rs:14` — `pub const PC20_STATE_SEED: &[u8] = b"pc20_state";` */
30
+ export declare const PC20_STATE_SEED: Buffer;
31
+ /** `state.rs:15` — `pub const PC20_SELECTOR: [u8; 4] = *b"PC20";` */
32
+ export declare const PC20_SELECTOR_BYTES: Buffer;
33
+ /**
34
+ * `Pc20State` account layout (`state.rs:125-134`):
35
+ *
36
+ * discriminator [8]
37
+ * source_asset [20] // Push-native EVM address, unpadded
38
+ * wrapped_mint [32]
39
+ * decimals [1]
40
+ * bump [1]
41
+ */
42
+ export declare const PC20_STATE_LEN: number;
43
+ export type Pc20State = {
44
+ /** Push-native PC20 address, checksummed. */
45
+ sourceAsset: `0x${string}`;
46
+ wrappedMint: PublicKey;
47
+ decimals: number;
48
+ bump: number;
49
+ };
50
+ /**
51
+ * Derive the wrapper mint for a Push-native PC20.
52
+ *
53
+ * The seed is the raw 20 bytes of the EVM address. A 32-byte left-padded form —
54
+ * the shape UniversalCore stores for EVM identities — would derive a different,
55
+ * entirely valid-looking PDA that no wrapper is ever minted to.
56
+ */
57
+ export declare function derivePC20Mint(programId: PublicKey, pushPC20Address: string): {
58
+ mint: PublicKey;
59
+ bump: number;
60
+ };
61
+ /** Derive the `Pc20State` PDA for a wrapper mint. */
62
+ export declare function derivePC20State(programId: PublicKey, mint: PublicKey): {
63
+ state: PublicKey;
64
+ bump: number;
65
+ };
66
+ /** Decode a `Pc20State` account. */
67
+ export declare function decodePc20State(data: Uint8Array): Pc20State;
68
+ /**
69
+ * Validate a supplied Solana PC20 mint against the program's own derivation and
70
+ * stored state.
71
+ *
72
+ * Three independent checks, all required — any one alone can be satisfied by a
73
+ * mint that is not the canonical wrapper:
74
+ * 1. the state PDA derives from the supplied mint;
75
+ * 2. the state's `wrapped_mint` equals the supplied mint;
76
+ * 3. re-deriving the mint from the state's `source_asset` reproduces it.
77
+ *
78
+ * Together they mean the mint, its state, and the Push source are mutually
79
+ * consistent — which is exactly what stops a mint copied from another
80
+ * deployment from passing.
81
+ */
82
+ export declare function validatePC20Mint(params: {
83
+ programId: PublicKey;
84
+ mint: PublicKey;
85
+ state: Pc20State;
86
+ statePda: PublicKey;
87
+ /** Push source UniversalCore returned for this wrapper. */
88
+ expectedSource: `0x${string}`;
89
+ }): void;
90
+ /**
91
+ * Accounts a Solana PC20 burn adds to the generic `send_universal_tx`
92
+ * instruction.
93
+ *
94
+ * Order is load-bearing: the program reads `remaining_accounts[0]` as the state
95
+ * and `[1]` as the mint (`pc20.rs:244-245`).
96
+ *
97
+ * `gatewayTokenAccount` is deliberately `null`. A PC20 inbound burns the
98
+ * wrapper via the mint authority rather than escrowing it in a gateway vault,
99
+ * so passing a vault here would be both unused and misleading.
100
+ */
101
+ export declare function buildPC20BurnAccounts(params: {
102
+ programId: PublicKey;
103
+ mint: PublicKey;
104
+ }): {
105
+ remainingAccounts: Array<{
106
+ pubkey: PublicKey;
107
+ isSigner: boolean;
108
+ isWritable: boolean;
109
+ }>;
110
+ gatewayTokenAccount: null;
111
+ };
112
+ /**
113
+ * Predicted destination mint for a first Push-to-Solana export.
114
+ *
115
+ * The SVM counterpart of the EVM `computeWrapperAddress` call. Unlike EVM,
116
+ * where the factory computes the address on chain, the mint is a pure PDA — so
117
+ * deriving it locally cannot drift, provided the seeds above stay in sync with
118
+ * the program.
119
+ */
120
+ export declare function predictSvmWrapperMint(programId: PublicKey, pushPC20Address: string): PublicKey;
121
+ /**
122
+ * Delivery instruction for a repeat export: SPL `TransferChecked` moving the
123
+ * freshly minted wrapper from the CEA ATA to the recipient's ATA.
124
+ *
125
+ * Settlement (`pc20.rs` id=5) mints into the CEA ATA and then CPIs exactly one
126
+ * instruction from the userData, signed by the CEA PDA — so delivery IS this
127
+ * one transfer. The signer flag is not encoded: `invoke_signed_gateway_instruction`
128
+ * marks whichever account equals the CEA authority as the signer.
129
+ *
130
+ * Only valid when the recipient ATA already exists — SPL transfers do not
131
+ * create destination accounts, and a failed CPI fails the whole settlement
132
+ * with the source already locked. Callers must verify existence first; on a
133
+ * first export the ATA cannot exist (its mint doesn't yet), so this is
134
+ * strictly a repeat-export tool.
135
+ */
136
+ export declare function buildPC20SvmDeliveryFields(params: {
137
+ mint: PublicKey;
138
+ ceaAta: PublicKey;
139
+ recipientAta: PublicKey;
140
+ ceaAuthority: PublicKey;
141
+ amount: bigint;
142
+ decimals: number;
143
+ }): {
144
+ targetProgram: `0x${string}`;
145
+ accounts: Array<{
146
+ pubkey: `0x${string}`;
147
+ isWritable: boolean;
148
+ }>;
149
+ ixData: Uint8Array;
150
+ instructionId: 2;
151
+ };
152
+ /** SPL Token program. */
153
+ export declare const SPL_TOKEN_PROGRAM_ID: PublicKey;
154
+ /**
155
+ * R3 (CEA → Push) burn payload: the outbound's execute-payload making the CEA
156
+ * self-call `send_universal_tx` to burn wrapper tokens it holds.
157
+ *
158
+ * The finalize dispatcher detects this shape (`execute.rs:511`:
159
+ * target == gateway && `is_pc20_burn_ix`) and routes to
160
+ * `route_pc20_burn_from_finalize_cea`, which enforces:
161
+ * - ix_data = discriminator("global:send_universal_tx") || Borsh
162
+ * `SendUniversalTxIxArgs { req, native_amount }` with native_amount == 0;
163
+ * - `req.recipient == push_account` — the unlocked token goes to the CEA's
164
+ * bound UEA on Push, nowhere else (forwarding happens via `req.payload`);
165
+ * - payload accounts exactly `[pc20_state ro, pc20_mint w, cea_ata w,
166
+ * token_program ro]` (`parse_pc20_cea_burn_accounts`).
167
+ *
168
+ * The revert recipient is the CEA PDA: a failed inbound re-mints the wrapper
169
+ * back to the CEA (`revert.rs` PC20 branch), restoring the pre-burn state.
170
+ */
171
+ export declare function buildPC20SvmCeaBurnPayload(params: {
172
+ gatewayProgram: PublicKey;
173
+ mint: PublicKey;
174
+ ceaAuthority: PublicKey;
175
+ ceaAta: PublicKey;
176
+ /** 20-byte UEA (the CEA's push_account) — program-enforced recipient. */
177
+ ueaAddress: `0x${string}`;
178
+ amount: bigint;
179
+ /** Push-side UniversalPayload bytes executed after the unlock (may be empty). */
180
+ pushPayload: Uint8Array;
181
+ }): {
182
+ targetProgram: `0x${string}`;
183
+ accounts: Array<{
184
+ pubkey: `0x${string}`;
185
+ isWritable: boolean;
186
+ }>;
187
+ ixData: Uint8Array;
188
+ instructionId: 2;
189
+ };
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ /**
3
+ * Solana PC20 support.
4
+ *
5
+ * Derived directly from the program source at
6
+ * `push-chain-gateway-contracts@pc20-3rd-iteration`:
7
+ * `contracts/svm-gateway/programs/universal-gateway/src/state.rs`
8
+ * `contracts/svm-gateway/programs/universal-gateway/src/instructions/pc20.rs`
9
+ *
10
+ * No built IDL artifact exists in that repo yet, so the PDA seeds and the
11
+ * `Pc20State` layout are mirrored here rather than read from an IDL. Both are
12
+ * pinned by unit test against the constants below; if the program changes them,
13
+ * the tests must be updated deliberately rather than silently drifting.
14
+ *
15
+ * Relevant program facts, and why each matters to the SDK:
16
+ *
17
+ * - `pc20_mint = PDA([b"pc20_mint", source_asset_20], program_id)` where
18
+ * `source_asset` is the **20-byte** Push-native address, not a 32-byte
19
+ * padded one. Getting this wrong yields a valid-looking but wrong mint.
20
+ * - `pc20_state = PDA([b"pc20_state", pc20_mint], program_id)`.
21
+ * - An inbound burn passes `remaining_accounts = [pc20_state, pc20_mint]`, in
22
+ * that order (`pc20.rs:244-245` reads index 0 then 1).
23
+ * - An inbound burn passes NO gateway token vault — the wrapper is burned,
24
+ * not escrowed.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.SPL_TOKEN_PROGRAM_ID = exports.PC20_STATE_LEN = exports.PC20_SELECTOR_BYTES = exports.PC20_STATE_SEED = exports.PC20_MINT_SEED = void 0;
28
+ exports.derivePC20Mint = derivePC20Mint;
29
+ exports.derivePC20State = derivePC20State;
30
+ exports.decodePc20State = decodePc20State;
31
+ exports.validatePC20Mint = validatePC20Mint;
32
+ exports.buildPC20BurnAccounts = buildPC20BurnAccounts;
33
+ exports.predictSvmWrapperMint = predictSvmWrapperMint;
34
+ exports.buildPC20SvmDeliveryFields = buildPC20SvmDeliveryFields;
35
+ exports.buildPC20SvmCeaBurnPayload = buildPC20SvmCeaBurnPayload;
36
+ const web3_js_1 = require("@solana/web3.js");
37
+ const viem_1 = require("viem");
38
+ const buffer_1 = require("buffer");
39
+ const enums_1 = require("../../../constants/enums");
40
+ const errors_1 = require("./errors");
41
+ /** `state.rs:13` — `pub const PC20_MINT_SEED: &[u8] = b"pc20_mint";` */
42
+ exports.PC20_MINT_SEED = buffer_1.Buffer.from('pc20_mint');
43
+ /** `state.rs:14` — `pub const PC20_STATE_SEED: &[u8] = b"pc20_state";` */
44
+ exports.PC20_STATE_SEED = buffer_1.Buffer.from('pc20_state');
45
+ /** `state.rs:15` — `pub const PC20_SELECTOR: [u8; 4] = *b"PC20";` */
46
+ exports.PC20_SELECTOR_BYTES = buffer_1.Buffer.from('PC20');
47
+ /**
48
+ * `Pc20State` account layout (`state.rs:125-134`):
49
+ *
50
+ * discriminator [8]
51
+ * source_asset [20] // Push-native EVM address, unpadded
52
+ * wrapped_mint [32]
53
+ * decimals [1]
54
+ * bump [1]
55
+ */
56
+ exports.PC20_STATE_LEN = 8 + 20 + 32 + 1 + 1;
57
+ /**
58
+ * Derive the wrapper mint for a Push-native PC20.
59
+ *
60
+ * The seed is the raw 20 bytes of the EVM address. A 32-byte left-padded form —
61
+ * the shape UniversalCore stores for EVM identities — would derive a different,
62
+ * entirely valid-looking PDA that no wrapper is ever minted to.
63
+ */
64
+ function derivePC20Mint(programId, pushPC20Address) {
65
+ const sourceAsset = toSourceAssetBytes(pushPC20Address);
66
+ const [mint, bump] = web3_js_1.PublicKey.findProgramAddressSync([exports.PC20_MINT_SEED, sourceAsset], programId);
67
+ return { mint, bump };
68
+ }
69
+ /** Derive the `Pc20State` PDA for a wrapper mint. */
70
+ function derivePC20State(programId, mint) {
71
+ const [state, bump] = web3_js_1.PublicKey.findProgramAddressSync([exports.PC20_STATE_SEED, mint.toBuffer()], programId);
72
+ return { state, bump };
73
+ }
74
+ /** Decode a `Pc20State` account. */
75
+ function decodePc20State(data) {
76
+ if (data.length < exports.PC20_STATE_LEN) {
77
+ throw new errors_1.InvalidPC20AddressError(`Pc20State account is ${data.length} bytes; expected at least ${exports.PC20_STATE_LEN}.`, { chain: String(enums_1.CHAIN.SOLANA_DEVNET) });
78
+ }
79
+ const buf = buffer_1.Buffer.from(data);
80
+ const sourceAsset = `0x${buf.subarray(8, 28).toString('hex')}`;
81
+ const wrappedMint = new web3_js_1.PublicKey(buf.subarray(28, 60));
82
+ return {
83
+ sourceAsset: (0, viem_1.getAddress)(sourceAsset),
84
+ wrappedMint,
85
+ decimals: buf[60],
86
+ bump: buf[61],
87
+ };
88
+ }
89
+ /**
90
+ * Validate a supplied Solana PC20 mint against the program's own derivation and
91
+ * stored state.
92
+ *
93
+ * Three independent checks, all required — any one alone can be satisfied by a
94
+ * mint that is not the canonical wrapper:
95
+ * 1. the state PDA derives from the supplied mint;
96
+ * 2. the state's `wrapped_mint` equals the supplied mint;
97
+ * 3. re-deriving the mint from the state's `source_asset` reproduces it.
98
+ *
99
+ * Together they mean the mint, its state, and the Push source are mutually
100
+ * consistent — which is exactly what stops a mint copied from another
101
+ * deployment from passing.
102
+ */
103
+ function validatePC20Mint(params) {
104
+ const { programId, mint, state, statePda, expectedSource } = params;
105
+ const { state: derivedState } = derivePC20State(programId, mint);
106
+ if (!derivedState.equals(statePda)) {
107
+ throw new errors_1.PC20RegistryMismatchError({
108
+ address: mint.toBase58(),
109
+ hint: `pc20_state PDA does not derive from this mint (expected ${derivedState.toBase58()}).`,
110
+ });
111
+ }
112
+ if (!state.wrappedMint.equals(mint)) {
113
+ throw new errors_1.PC20RegistryMismatchError({
114
+ address: mint.toBase58(),
115
+ hint: `Pc20State.wrapped_mint is ${state.wrappedMint.toBase58()}, not the supplied mint.`,
116
+ });
117
+ }
118
+ const { mint: derivedMint } = derivePC20Mint(programId, state.sourceAsset);
119
+ if (!derivedMint.equals(mint)) {
120
+ throw new errors_1.PC20RegistryMismatchError({
121
+ address: mint.toBase58(),
122
+ hint: `Re-deriving from Pc20State.source_asset yields ${derivedMint.toBase58()}.`,
123
+ });
124
+ }
125
+ if ((0, viem_1.getAddress)(state.sourceAsset) !== (0, viem_1.getAddress)(expectedSource)) {
126
+ throw new errors_1.PC20RegistryMismatchError({
127
+ address: mint.toBase58(),
128
+ hint: `Pc20State.source_asset (${state.sourceAsset}) does not match the Push ` +
129
+ `source UniversalCore returned (${expectedSource}).`,
130
+ });
131
+ }
132
+ }
133
+ /**
134
+ * Accounts a Solana PC20 burn adds to the generic `send_universal_tx`
135
+ * instruction.
136
+ *
137
+ * Order is load-bearing: the program reads `remaining_accounts[0]` as the state
138
+ * and `[1]` as the mint (`pc20.rs:244-245`).
139
+ *
140
+ * `gatewayTokenAccount` is deliberately `null`. A PC20 inbound burns the
141
+ * wrapper via the mint authority rather than escrowing it in a gateway vault,
142
+ * so passing a vault here would be both unused and misleading.
143
+ */
144
+ function buildPC20BurnAccounts(params) {
145
+ const { state } = derivePC20State(params.programId, params.mint);
146
+ return {
147
+ // Flags per route_pc20_universal_tx (deposit.rs): the state is read-only,
148
+ // the mint must be writable (it is burned against), and neither may sign —
149
+ // the program rejects any deviation, so these are load-bearing.
150
+ remainingAccounts: [
151
+ { pubkey: state, isSigner: false, isWritable: false },
152
+ { pubkey: params.mint, isSigner: false, isWritable: true },
153
+ ],
154
+ gatewayTokenAccount: null,
155
+ };
156
+ }
157
+ /**
158
+ * Predicted destination mint for a first Push-to-Solana export.
159
+ *
160
+ * The SVM counterpart of the EVM `computeWrapperAddress` call. Unlike EVM,
161
+ * where the factory computes the address on chain, the mint is a pure PDA — so
162
+ * deriving it locally cannot drift, provided the seeds above stay in sync with
163
+ * the program.
164
+ */
165
+ function predictSvmWrapperMint(programId, pushPC20Address) {
166
+ return derivePC20Mint(programId, pushPC20Address).mint;
167
+ }
168
+ /**
169
+ * Delivery instruction for a repeat export: SPL `TransferChecked` moving the
170
+ * freshly minted wrapper from the CEA ATA to the recipient's ATA.
171
+ *
172
+ * Settlement (`pc20.rs` id=5) mints into the CEA ATA and then CPIs exactly one
173
+ * instruction from the userData, signed by the CEA PDA — so delivery IS this
174
+ * one transfer. The signer flag is not encoded: `invoke_signed_gateway_instruction`
175
+ * marks whichever account equals the CEA authority as the signer.
176
+ *
177
+ * Only valid when the recipient ATA already exists — SPL transfers do not
178
+ * create destination accounts, and a failed CPI fails the whole settlement
179
+ * with the source already locked. Callers must verify existence first; on a
180
+ * first export the ATA cannot exist (its mint doesn't yet), so this is
181
+ * strictly a repeat-export tool.
182
+ */
183
+ function buildPC20SvmDeliveryFields(params) {
184
+ // SPL Token TransferChecked: index 12, data = u8 || u64 LE amount || u8 decimals.
185
+ const ixData = new Uint8Array(10);
186
+ ixData[0] = 12;
187
+ new DataView(ixData.buffer).setBigUint64(1, params.amount, true);
188
+ ixData[9] = params.decimals;
189
+ const hex = (k) => `0x${buffer_1.Buffer.from(k.toBytes()).toString('hex')}`;
190
+ return {
191
+ targetProgram: hex(exports.SPL_TOKEN_PROGRAM_ID),
192
+ accounts: [
193
+ { pubkey: hex(params.ceaAta), isWritable: true }, // source
194
+ { pubkey: hex(params.mint), isWritable: false }, // mint (checked)
195
+ { pubkey: hex(params.recipientAta), isWritable: true }, // destination
196
+ { pubkey: hex(params.ceaAuthority), isWritable: false }, // owner → CPI signer
197
+ ],
198
+ ixData,
199
+ instructionId: 2,
200
+ };
201
+ }
202
+ /** SPL Token program. */
203
+ exports.SPL_TOKEN_PROGRAM_ID = new web3_js_1.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
204
+ /**
205
+ * R3 (CEA → Push) burn payload: the outbound's execute-payload making the CEA
206
+ * self-call `send_universal_tx` to burn wrapper tokens it holds.
207
+ *
208
+ * The finalize dispatcher detects this shape (`execute.rs:511`:
209
+ * target == gateway && `is_pc20_burn_ix`) and routes to
210
+ * `route_pc20_burn_from_finalize_cea`, which enforces:
211
+ * - ix_data = discriminator("global:send_universal_tx") || Borsh
212
+ * `SendUniversalTxIxArgs { req, native_amount }` with native_amount == 0;
213
+ * - `req.recipient == push_account` — the unlocked token goes to the CEA's
214
+ * bound UEA on Push, nowhere else (forwarding happens via `req.payload`);
215
+ * - payload accounts exactly `[pc20_state ro, pc20_mint w, cea_ata w,
216
+ * token_program ro]` (`parse_pc20_cea_burn_accounts`).
217
+ *
218
+ * The revert recipient is the CEA PDA: a failed inbound re-mints the wrapper
219
+ * back to the CEA (`revert.rs` PC20 branch), restoring the pre-burn state.
220
+ */
221
+ function buildPC20SvmCeaBurnPayload(params) {
222
+ const { state } = derivePC20State(params.gatewayProgram, params.mint);
223
+ // Anchor discriminator: first 8 bytes of SHA-256("global:send_universal_tx").
224
+ const discriminator = sha256(new TextEncoder().encode('global:send_universal_tx')).subarray(0, 8);
225
+ const recipient = buffer_1.Buffer.from(params.ueaAddress.slice(2), 'hex');
226
+ if (recipient.length !== 20) {
227
+ throw new errors_1.InvalidPC20AddressError('UEA must be a 20-byte address.', {
228
+ address: params.ueaAddress,
229
+ });
230
+ }
231
+ // Borsh SendUniversalTxIxArgs { req: UniversalTxRequest, native_amount: u64 }
232
+ // req: recipient [u8;20] || token [32] || amount u64 LE || payload Vec<u8>
233
+ // || revert_recipient [32] || signature_data Vec<u8> (state.rs:67)
234
+ const payloadLen = params.pushPayload.length;
235
+ const ixData = new Uint8Array(8 + 20 + 32 + 8 + 4 + payloadLen + 32 + 4 + 8);
236
+ const view = new DataView(ixData.buffer);
237
+ let o = 0;
238
+ ixData.set(discriminator, o);
239
+ o += 8;
240
+ ixData.set(recipient, o);
241
+ o += 20;
242
+ ixData.set(params.mint.toBytes(), o);
243
+ o += 32;
244
+ view.setBigUint64(o, params.amount, true);
245
+ o += 8;
246
+ view.setUint32(o, payloadLen, true);
247
+ o += 4; // Borsh Vec<u8>: u32 LE length
248
+ ixData.set(params.pushPayload, o);
249
+ o += payloadLen;
250
+ // Revert re-mints to the CEA, restoring pre-burn state.
251
+ ixData.set(params.ceaAuthority.toBytes(), o);
252
+ o += 32;
253
+ view.setUint32(o, 0, true);
254
+ o += 4; // signature_data: empty Vec<u8>
255
+ view.setBigUint64(o, BigInt(0), true); // native_amount: must be 0
256
+ const hex = (k) => `0x${buffer_1.Buffer.from(k.toBytes()).toString('hex')}`;
257
+ return {
258
+ targetProgram: hex(params.gatewayProgram),
259
+ accounts: [
260
+ { pubkey: hex(state), isWritable: false },
261
+ { pubkey: hex(params.mint), isWritable: true },
262
+ { pubkey: hex(params.ceaAta), isWritable: true },
263
+ { pubkey: hex(exports.SPL_TOKEN_PROGRAM_ID), isWritable: false },
264
+ ],
265
+ ixData,
266
+ instructionId: 2,
267
+ };
268
+ }
269
+ /** Minimal sha256 over bytes, returned as bytes. */
270
+ function sha256(data) {
271
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
272
+ const { createHash } = require('crypto');
273
+ return createHash('sha256').update(data).digest();
274
+ }
275
+ /** Normalize a Push-native EVM address into the 20 raw bytes used as a seed. */
276
+ function toSourceAssetBytes(address) {
277
+ let checksummed;
278
+ try {
279
+ checksummed = (0, viem_1.getAddress)(address);
280
+ }
281
+ catch {
282
+ throw new errors_1.InvalidPC20AddressError('Push-native PC20 address is not a valid 20-byte EVM address.', { address });
283
+ }
284
+ return buffer_1.Buffer.from(checksummed.slice(2), 'hex');
285
+ }
286
+ //# sourceMappingURL=svm.js.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * PC20 outbound tracking.
3
+ *
4
+ * `externalAssetAddr` alone is not a reliable source for the destination
5
+ * wrapper address on a PC20 export:
6
+ *
7
+ * - it is empty when the outbound is first created;
8
+ * - it is normally backfilled once the destination wrapper is *observed*,
9
+ * which only happens when a wrapper is newly deployed;
10
+ * - a repeat export deploys nothing, so nothing is observed and the field can
11
+ * stay empty indefinitely.
12
+ *
13
+ * The wrapper is therefore resolved from UniversalCore whenever the observation
14
+ * did not supply one. `pc20ContractAddress` (the locked Push-native source) is
15
+ * the stable identifier that makes the lookup possible.
16
+ */
17
+ import type { OutboundTxV2 } from '../../../generated/uexecutor/v2/types';
18
+ import type { OrchestratorContext } from '../context';
19
+ import { CHAIN } from '../../../constants/enums';
20
+ import { type PC20ResolverOptions } from './resolver';
21
+ /** Shape of the fields this module needs, so callers can pass partial fixtures. */
22
+ export type PC20OutboundView = Pick<OutboundTxV2, 'isPc20' | 'pc20ContractAddress' | 'externalAssetAddr' | 'destinationChain'> & {
23
+ observedTx?: {
24
+ pc20WrapperAddress?: string;
25
+ } | undefined;
26
+ };
27
+ /**
28
+ * Best available destination wrapper address for a PC20 outbound, in
29
+ * chain-native form (checksummed hex on EVM, base58 on Solana).
30
+ *
31
+ * Resolution order, cheapest first:
32
+ * 1. the settlement observation, when a wrapper was newly deployed;
33
+ * 2. `externalAssetAddr`, once backfilled;
34
+ * 3. UniversalCore, keyed by the Push-native source.
35
+ *
36
+ * Returns `undefined` rather than throwing when nothing resolves — a receipt
37
+ * for an in-flight outbound legitimately has no wrapper yet, and failing
38
+ * tracking over a cosmetic field would be worse than omitting it.
39
+ */
40
+ export declare function resolvePC20WrapperForReceipt(ctx: OrchestratorContext, outbound: PC20OutboundView, opts: PC20ResolverOptions): Promise<string | undefined>;
41
+ /**
42
+ * Strict namespace resolution for callers that must not silently degrade.
43
+ *
44
+ * @throws {PC20UnknownChainNamespaceError} when the namespace maps to no chain.
45
+ */
46
+ export declare function requireDestinationChain(namespace: string): CHAIN;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ /**
3
+ * PC20 outbound tracking.
4
+ *
5
+ * `externalAssetAddr` alone is not a reliable source for the destination
6
+ * wrapper address on a PC20 export:
7
+ *
8
+ * - it is empty when the outbound is first created;
9
+ * - it is normally backfilled once the destination wrapper is *observed*,
10
+ * which only happens when a wrapper is newly deployed;
11
+ * - a repeat export deploys nothing, so nothing is observed and the field can
12
+ * stay empty indefinitely.
13
+ *
14
+ * The wrapper is therefore resolved from UniversalCore whenever the observation
15
+ * did not supply one. `pc20ContractAddress` (the locked Push-native source) is
16
+ * the stable identifier that makes the lookup possible.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.resolvePC20WrapperForReceipt = resolvePC20WrapperForReceipt;
20
+ exports.requireDestinationChain = requireDestinationChain;
21
+ const context_1 = require("../context");
22
+ const chain_namespace_1 = require("./chain-namespace");
23
+ const resolver_1 = require("./resolver");
24
+ const errors_1 = require("./errors");
25
+ /**
26
+ * Best available destination wrapper address for a PC20 outbound, in
27
+ * chain-native form (checksummed hex on EVM, base58 on Solana).
28
+ *
29
+ * Resolution order, cheapest first:
30
+ * 1. the settlement observation, when a wrapper was newly deployed;
31
+ * 2. `externalAssetAddr`, once backfilled;
32
+ * 3. UniversalCore, keyed by the Push-native source.
33
+ *
34
+ * Returns `undefined` rather than throwing when nothing resolves — a receipt
35
+ * for an in-flight outbound legitimately has no wrapper yet, and failing
36
+ * tracking over a cosmetic field would be worse than omitting it.
37
+ */
38
+ async function resolvePC20WrapperForReceipt(ctx, outbound, opts) {
39
+ if (!outbound.isPc20)
40
+ return undefined;
41
+ const observed = outbound.observedTx?.pc20WrapperAddress;
42
+ if (observed)
43
+ return normalizeForChain(outbound.destinationChain, observed);
44
+ if (outbound.externalAssetAddr) {
45
+ return normalizeForChain(outbound.destinationChain, outbound.externalAssetAddr);
46
+ }
47
+ if (!outbound.pc20ContractAddress) {
48
+ (0, context_1.printLog)(ctx, '[pc20] outbound is flagged isPc20 but carries no pc20ContractAddress; ' +
49
+ 'cannot resolve the destination wrapper');
50
+ return undefined;
51
+ }
52
+ // `destinationChain` arrives as an untyped string. It must go through the
53
+ // namespace mapping — passing it to the registry verbatim would return a zero
54
+ // wrapper, indistinguishable from "not deployed yet".
55
+ const chain = (0, chain_namespace_1.tryNamespaceToChain)(outbound.destinationChain);
56
+ if (!chain) {
57
+ (0, context_1.printLog)(ctx, `[pc20] unmappable destination namespace "${outbound.destinationChain}"; ` +
58
+ 'skipping wrapper resolution');
59
+ return undefined;
60
+ }
61
+ try {
62
+ const registered = await (0, resolver_1.getRegisteredWrapper)(outbound.pc20ContractAddress, chain, opts);
63
+ return registered?.address;
64
+ }
65
+ catch (err) {
66
+ (0, context_1.printLog)(ctx, `[pc20] wrapper resolution failed for ${outbound.pc20ContractAddress} on ` +
67
+ `${chain}: ${err instanceof Error ? err.message : String(err)}`);
68
+ return undefined;
69
+ }
70
+ }
71
+ /**
72
+ * Strict namespace resolution for callers that must not silently degrade.
73
+ *
74
+ * @throws {PC20UnknownChainNamespaceError} when the namespace maps to no chain.
75
+ */
76
+ function requireDestinationChain(namespace) {
77
+ const chain = (0, chain_namespace_1.tryNamespaceToChain)(namespace);
78
+ if (!chain) {
79
+ throw new errors_1.PC20UnknownChainNamespaceError(namespace, {
80
+ hint: 'Destination chain on the outbound record is not a supported CAIP-2 id.',
81
+ });
82
+ }
83
+ return chain;
84
+ }
85
+ /**
86
+ * Present an address in the destination chain's native form.
87
+ *
88
+ * SVM identities travel as `0x`-prefixed hex internally (that is how the
89
+ * Cosmos keeper indexes them) but users expect base58.
90
+ */
91
+ function normalizeForChain(namespace, address) {
92
+ const chain = (0, chain_namespace_1.tryNamespaceToChain)(namespace);
93
+ if (!chain)
94
+ return address;
95
+ try {
96
+ if ((0, chain_namespace_1.vmForChain)(chain) === 'SVM' && address.startsWith('0x')) {
97
+ // Reuse the codec so hex→base58 has exactly one implementation.
98
+ const { pc20Bytes32ToAddress } = require('./address-codec');
99
+ return pc20Bytes32ToAddress(chain, address);
100
+ }
101
+ const { getAddress } = require('viem');
102
+ return getAddress(address);
103
+ }
104
+ catch {
105
+ // A malformed address from the node is not worth failing a receipt over.
106
+ return address;
107
+ }
108
+ }
109
+ //# sourceMappingURL=tracking.js.map
@@ -54,7 +54,7 @@ function runPreflight(opts) {
54
54
  // 1. PRC-20 burn-balance check (skipped when burnAmount = 0 — R3 paths
55
55
  // structurally hold zero of the source-chain native PRC-20; see plan §9 #4).
56
56
  if (burnAmount && burnAmount > BigInt(0) && burnToken) {
57
- const onHand = prc20Balance !== null && prc20Balance !== void 0 ? prc20Balance : BigInt(0);
57
+ const onHand = prc20Balance ?? BigInt(0);
58
58
  const sufficient = onHand >= burnAmount;
59
59
  const shortfall = sufficient ? BigInt(0) : burnAmount - onHand;
60
60
  (0, context_1.fireProgressHook)(ctx, HOOK_PRE_INFO, burnAmount, onHand, sufficient, ueaAddress, pathTag, {
@@ -21,7 +21,7 @@ function pickWaitHooks(route) {
21
21
  polling: (chain, elapsed, pushTxHash) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_209_02](chain, elapsed, pushTxHash),
22
22
  success: (details) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_299_01](details),
23
23
  timeout: (chain, elapsed, pushTxHash) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_299_03](chain, elapsed, pushTxHash),
24
- failed: (chain, msg, metadata) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_299_02](chain, msg, undefined, metadata === null || metadata === void 0 ? void 0 : metadata.pushTxHash, metadata === null || metadata === void 0 ? void 0 : metadata.txHash),
24
+ failed: (chain, msg, metadata) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_299_02](chain, msg, undefined, metadata?.pushTxHash, metadata?.txHash),
25
25
  };
26
26
  }
27
27
  if (route === route_detector_1.TransactionRoute.CEA_TO_PUSH) {
@@ -36,7 +36,7 @@ function pickWaitHooks(route) {
36
36
  // R3 inbound; pass phase='outbound' so the title reflects the source
37
37
  // chain instead of "Push Chain Inbound …".
38
38
  timeout: (chain, elapsed, pushTxHash) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_03](chain, elapsed, 'outbound', pushTxHash),
39
- failed: (chain, msg, metadata) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](msg, 'outbound', chain, undefined, metadata === null || metadata === void 0 ? void 0 : metadata.pushTxHash, metadata === null || metadata === void 0 ? void 0 : metadata.txHash),
39
+ failed: (chain, msg, metadata) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](msg, 'outbound', chain, undefined, metadata?.pushTxHash, metadata?.txHash),
40
40
  };
41
41
  }
42
42
  // Route 4 (CEA_TO_CEA) has no spec'd ID range yet; cascade hops and