@unicitylabs/bridge-plugin 0.2.0-dev.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.
Files changed (102) hide show
  1. package/DEMO.md +255 -0
  2. package/README.md +89 -0
  3. package/lib/BridgeLockJustification.d.ts +31 -0
  4. package/lib/BridgeLockJustification.js +41 -0
  5. package/lib/BridgeMintJustificationVerifier.d.ts +12 -0
  6. package/lib/BridgeMintJustificationVerifier.js +35 -0
  7. package/lib/LockMintJustificationVerifier.d.ts +42 -0
  8. package/lib/LockMintJustificationVerifier.js +108 -0
  9. package/lib/address.d.ts +13 -0
  10. package/lib/address.js +71 -0
  11. package/lib/bridge-back/abi.d.ts +30 -0
  12. package/lib/bridge-back/abi.js +89 -0
  13. package/lib/bridge-back/burn.d.ts +79 -0
  14. package/lib/bridge-back/burn.js +78 -0
  15. package/lib/bridge-back/burned-blob.d.ts +6 -0
  16. package/lib/bridge-back/burned-blob.js +16 -0
  17. package/lib/bridge-back/cbor.d.ts +32 -0
  18. package/lib/bridge-back/cbor.js +146 -0
  19. package/lib/bridge-back/derivations.d.ts +112 -0
  20. package/lib/bridge-back/derivations.js +201 -0
  21. package/lib/bridge-back/index.d.ts +11 -0
  22. package/lib/bridge-back/index.js +11 -0
  23. package/lib/cli/main.d.ts +2 -0
  24. package/lib/cli/main.js +149 -0
  25. package/lib/cli/scenario.d.ts +36 -0
  26. package/lib/cli/scenario.js +94 -0
  27. package/lib/config.d.ts +25 -0
  28. package/lib/config.js +1 -0
  29. package/lib/contract-call.d.ts +13 -0
  30. package/lib/contract-call.js +19 -0
  31. package/lib/evm/EvmRpcClient.d.ts +25 -0
  32. package/lib/evm/EvmRpcClient.js +54 -0
  33. package/lib/evm/config.d.ts +6 -0
  34. package/lib/evm/config.js +6 -0
  35. package/lib/evm/family.d.ts +3 -0
  36. package/lib/evm/family.js +21 -0
  37. package/lib/evm/index.d.ts +6 -0
  38. package/lib/evm/index.js +6 -0
  39. package/lib/evm/presentation.d.ts +4 -0
  40. package/lib/evm/presentation.js +21 -0
  41. package/lib/evm/providers.d.ts +3 -0
  42. package/lib/evm/providers.js +9 -0
  43. package/lib/evm/signer.d.ts +43 -0
  44. package/lib/evm/signer.js +91 -0
  45. package/lib/families.d.ts +4 -0
  46. package/lib/families.js +11 -0
  47. package/lib/family.d.ts +16 -0
  48. package/lib/family.js +1 -0
  49. package/lib/hex.d.ts +4 -0
  50. package/lib/hex.js +33 -0
  51. package/lib/identifiers.d.ts +14 -0
  52. package/lib/identifiers.js +22 -0
  53. package/lib/index.d.ts +48 -0
  54. package/lib/index.js +55 -0
  55. package/lib/lock-event.d.ts +28 -0
  56. package/lib/lock-event.js +48 -0
  57. package/lib/source-chain.d.ts +29 -0
  58. package/lib/source-chain.js +1 -0
  59. package/lib/tron/TronRpcClient.d.ts +39 -0
  60. package/lib/tron/TronRpcClient.js +100 -0
  61. package/lib/tron/config.d.ts +11 -0
  62. package/lib/tron/config.js +31 -0
  63. package/lib/tron/family.d.ts +4 -0
  64. package/lib/tron/family.js +18 -0
  65. package/lib/tron/presentation.d.ts +10 -0
  66. package/lib/tron/presentation.js +18 -0
  67. package/lib/tron/providers.d.ts +46 -0
  68. package/lib/tron/providers.js +32 -0
  69. package/lib/tron/signer.d.ts +166 -0
  70. package/lib/tron/signer.js +238 -0
  71. package/lib/value.d.ts +23 -0
  72. package/lib/value.js +58 -0
  73. package/lib/wallet/allowance.d.ts +20 -0
  74. package/lib/wallet/allowance.js +27 -0
  75. package/lib/wallet/backing.d.ts +2 -0
  76. package/lib/wallet/backing.js +15 -0
  77. package/lib/wallet/facade.d.ts +97 -0
  78. package/lib/wallet/facade.js +100 -0
  79. package/lib/wallet/finality.d.ts +8 -0
  80. package/lib/wallet/finality.js +28 -0
  81. package/lib/wallet/index.d.ts +25 -0
  82. package/lib/wallet/index.js +25 -0
  83. package/lib/wallet/manifest.d.ts +69 -0
  84. package/lib/wallet/manifest.js +81 -0
  85. package/lib/wallet/manifests.d.ts +31 -0
  86. package/lib/wallet/manifests.js +70 -0
  87. package/lib/wallet/payout.d.ts +5 -0
  88. package/lib/wallet/payout.js +14 -0
  89. package/lib/wallet/registry.d.ts +40 -0
  90. package/lib/wallet/registry.js +34 -0
  91. package/lib/wallet/return-client.d.ts +91 -0
  92. package/lib/wallet/return-client.js +100 -0
  93. package/lib/wallet/self-mint-verifier.d.ts +10 -0
  94. package/lib/wallet/self-mint-verifier.js +5 -0
  95. package/lib/wallet/signer.d.ts +18 -0
  96. package/lib/wallet/signer.js +1 -0
  97. package/lib/wallet/source-adapter.d.ts +33 -0
  98. package/lib/wallet/source-adapter.js +99 -0
  99. package/lib/wallet/token-plugin.d.ts +4 -0
  100. package/lib/wallet/token-plugin.js +20 -0
  101. package/manifests/bridges.nile.json +23 -0
  102. package/package.json +71 -0
@@ -0,0 +1,21 @@
1
+ import { toEvmAddressHex } from '../address.js';
2
+ import { EVM_DEFAULT_CONFIRMATIONS } from './config.js';
3
+ import { EvmJsonRpcClient } from './EvmRpcClient.js';
4
+ import { evmPresentation } from './presentation.js';
5
+ export function evmChainRef(chainId) {
6
+ return `eip155:${chainId}`;
7
+ }
8
+ export const evmFamily = {
9
+ family: 'eip155',
10
+ chainName: 'Ethereum',
11
+ defaultConfirmations: EVM_DEFAULT_CONFIRMATIONS,
12
+ blockSeconds: 12,
13
+ chainRef: evmChainRef,
14
+ normalizeAddress: toEvmAddressHex,
15
+ createRpc: ({ rpcUrl }) => {
16
+ if (!rpcUrl)
17
+ throw new Error('An Ethereum-family bridge needs an rpcUrl.');
18
+ return new EvmJsonRpcClient({ rpcUrl });
19
+ },
20
+ presentation: evmPresentation,
21
+ };
@@ -0,0 +1,6 @@
1
+ export * from './config.js';
2
+ export * from './EvmRpcClient.js';
3
+ export * from './family.js';
4
+ export * from './presentation.js';
5
+ export * from './providers.js';
6
+ export * from './signer.js';
@@ -0,0 +1,6 @@
1
+ export * from './config.js';
2
+ export * from './EvmRpcClient.js';
3
+ export * from './family.js';
4
+ export * from './presentation.js';
5
+ export * from './providers.js';
6
+ export * from './signer.js';
@@ -0,0 +1,4 @@
1
+ import type { BridgePresentation } from '@unicitylabs/bridge-core';
2
+ export declare function evmExplorerTxUrl(chainId: number, txid: string): string;
3
+ export declare function isValidEvmAddress(addr: string): boolean;
4
+ export declare function evmPresentation(chainId: number): BridgePresentation;
@@ -0,0 +1,21 @@
1
+ import { ETHEREUM_MAINNET_CHAIN_ID, SEPOLIA_CHAIN_ID } from './config.js';
2
+ const EXPLORERS = {
3
+ [ETHEREUM_MAINNET_CHAIN_ID]: 'https://etherscan.io',
4
+ [SEPOLIA_CHAIN_ID]: 'https://sepolia.etherscan.io',
5
+ };
6
+ function with0x(hex) {
7
+ return hex.startsWith('0x') || hex.startsWith('0X') ? hex : `0x${hex}`;
8
+ }
9
+ export function evmExplorerTxUrl(chainId, txid) {
10
+ const base = EXPLORERS[chainId] ?? 'https://blockscan.com';
11
+ return `${base}/tx/${with0x(txid)}`;
12
+ }
13
+ export function isValidEvmAddress(addr) {
14
+ return /^0x[0-9a-fA-F]{40}$/.test(addr.trim());
15
+ }
16
+ export function evmPresentation(chainId) {
17
+ return {
18
+ explorerTxUrl: (txid) => evmExplorerTxUrl(chainId, txid),
19
+ validateAddress: (addr) => isValidEvmAddress(addr),
20
+ };
21
+ }
@@ -0,0 +1,3 @@
1
+ import type { SourceWalletProvider } from '../wallet/signer.js';
2
+ import { type EvmWindow } from './signer.js';
3
+ export declare function injectedEvmProvider(win?: EvmWindow): SourceWalletProvider;
@@ -0,0 +1,9 @@
1
+ import { InjectedEvmSigner } from './signer.js';
2
+ export function injectedEvmProvider(win = globalThis) {
3
+ return {
4
+ id: 'injected-evm',
5
+ name: 'MetaMask',
6
+ isAvailable: () => Boolean(win.ethereum),
7
+ create: (chainId) => new InjectedEvmSigner(win, chainId),
8
+ };
9
+ }
@@ -0,0 +1,43 @@
1
+ import { type ContractCall } from '../contract-call.js';
2
+ import type { SourceSigner, WalletChange } from '../wallet/signer.js';
3
+ export interface Eip1193Provider {
4
+ request(args: {
5
+ method: string;
6
+ params?: unknown[];
7
+ }): Promise<unknown>;
8
+ on?(event: string, listener: (...args: unknown[]) => void): void;
9
+ removeListener?(event: string, listener: (...args: unknown[]) => void): void;
10
+ }
11
+ export interface EvmWindow {
12
+ ethereum?: Eip1193Provider;
13
+ }
14
+ export declare class InjectedEvmSigner implements SourceSigner {
15
+ private readonly win;
16
+ private readonly expectedChainId?;
17
+ constructor(win?: EvmWindow, expectedChainId?: number | undefined);
18
+ private provider;
19
+ connect(): Promise<string>;
20
+ private switchTo;
21
+ getAddress(): Promise<string>;
22
+ getNetwork(): Promise<number>;
23
+ onChange(cb: (e: WalletChange) => void): () => void;
24
+ sendCall(call: ContractCall): Promise<string>;
25
+ }
26
+ export interface EvmTxSender {
27
+ getAddress(): Promise<string>;
28
+ sendTransaction(tx: {
29
+ to: string;
30
+ data: string;
31
+ }): Promise<{
32
+ hash: string;
33
+ }>;
34
+ }
35
+ export declare class ManagedEvmSigner implements SourceSigner {
36
+ private readonly sender;
37
+ private readonly chainId;
38
+ constructor(sender: EvmTxSender, chainId: number);
39
+ connect(): Promise<string>;
40
+ getAddress(): Promise<string>;
41
+ getNetwork(): Promise<number>;
42
+ sendCall(call: ContractCall): Promise<string>;
43
+ }
@@ -0,0 +1,91 @@
1
+ import { encodeCallData } from '../contract-call.js';
2
+ export class InjectedEvmSigner {
3
+ win;
4
+ expectedChainId;
5
+ constructor(win = globalThis, expectedChainId) {
6
+ this.win = win;
7
+ this.expectedChainId = expectedChainId;
8
+ }
9
+ provider() {
10
+ const p = this.win.ethereum;
11
+ if (!p) {
12
+ throw new Error('No Ethereum wallet found. Install MetaMask and reload.');
13
+ }
14
+ return p;
15
+ }
16
+ async connect() {
17
+ const accounts = (await this.provider().request({ method: 'eth_requestAccounts' }));
18
+ const address = accounts?.[0];
19
+ if (!address) {
20
+ throw new Error('The Ethereum wallet is locked or no account is selected.');
21
+ }
22
+ if (this.expectedChainId !== undefined && (await this.getNetwork()) !== this.expectedChainId) {
23
+ await this.switchTo(this.expectedChainId);
24
+ }
25
+ return address;
26
+ }
27
+ async switchTo(chainId) {
28
+ try {
29
+ await this.provider().request({ method: 'wallet_switchEthereumChain', params: [{ chainId: `0x${chainId.toString(16)}` }] });
30
+ }
31
+ catch {
32
+ return;
33
+ }
34
+ }
35
+ async getAddress() {
36
+ const accounts = (await this.provider().request({ method: 'eth_accounts' }));
37
+ return accounts?.[0] ?? this.connect();
38
+ }
39
+ async getNetwork() {
40
+ const id = (await this.provider().request({ method: 'eth_chainId' }));
41
+ return Number.parseInt(id, 16);
42
+ }
43
+ onChange(cb) {
44
+ const p = this.provider();
45
+ if (!p.on || !p.removeListener)
46
+ return () => { };
47
+ const listeners = [
48
+ ['accountsChanged', () => cb({ kind: 'accountsChanged' })],
49
+ ['chainChanged', () => cb({ kind: 'chainChanged' })],
50
+ ['disconnect', () => cb({ kind: 'disconnect' })],
51
+ ];
52
+ for (const [event, listener] of listeners)
53
+ p.on(event, listener);
54
+ return () => {
55
+ for (const [event, listener] of listeners)
56
+ p.removeListener?.(event, listener);
57
+ };
58
+ }
59
+ async sendCall(call) {
60
+ const from = await this.getAddress();
61
+ const hash = (await this.provider().request({
62
+ method: 'eth_sendTransaction',
63
+ params: [{ from, to: `0x${call.contractHex}`, data: `0x${encodeCallData(call)}` }],
64
+ }));
65
+ if (!hash) {
66
+ throw new Error(`The wallet returned no transaction hash for ${call.functionSignature}.`);
67
+ }
68
+ return hash;
69
+ }
70
+ }
71
+ export class ManagedEvmSigner {
72
+ sender;
73
+ chainId;
74
+ constructor(sender, chainId) {
75
+ this.sender = sender;
76
+ this.chainId = chainId;
77
+ }
78
+ connect() {
79
+ return this.sender.getAddress();
80
+ }
81
+ getAddress() {
82
+ return this.sender.getAddress();
83
+ }
84
+ async getNetwork() {
85
+ return this.chainId;
86
+ }
87
+ async sendCall(call) {
88
+ const tx = await this.sender.sendTransaction({ to: `0x${call.contractHex}`, data: `0x${encodeCallData(call)}` });
89
+ return tx.hash;
90
+ }
91
+ }
@@ -0,0 +1,4 @@
1
+ import type { ChainFamily } from '@unicitylabs/bridge-core';
2
+ import type { ChainFamilyAdapter } from './family.js';
3
+ export type { ChainFamilyAdapter, RpcOptions } from './family.js';
4
+ export declare function chainFamily(family: ChainFamily): ChainFamilyAdapter;
@@ -0,0 +1,11 @@
1
+ import { evmFamily } from './evm/family.js';
2
+ import { tronFamily } from './tron/family.js';
3
+ export function chainFamily(family) {
4
+ switch (family) {
5
+ case 'tron':
6
+ return tronFamily;
7
+ case 'eip155':
8
+ return evmFamily;
9
+ }
10
+ throw new Error(`Unsupported chain family: ${String(family)}`);
11
+ }
@@ -0,0 +1,16 @@
1
+ import type { BridgePresentation, ChainFamily } from '@unicitylabs/bridge-core';
2
+ import type { ConstantCaller, SourceChainRpc } from './source-chain.js';
3
+ export interface RpcOptions {
4
+ readonly rpcUrl?: string;
5
+ readonly apiKey?: string;
6
+ }
7
+ export interface ChainFamilyAdapter {
8
+ readonly family: ChainFamily;
9
+ readonly chainName: string;
10
+ readonly defaultConfirmations: number;
11
+ readonly blockSeconds: number;
12
+ chainRef(chainId: number): string;
13
+ normalizeAddress(address: string): string;
14
+ createRpc(options: RpcOptions): SourceChainRpc & ConstantCaller;
15
+ presentation(chainId: number): BridgePresentation;
16
+ }
package/lib/family.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/lib/hex.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ /** Small hex helpers (lowercase, no `0x`). */
2
+ export declare function toHex(bytes: Uint8Array): string;
3
+ export declare function fromHex(hex: string): Uint8Array;
4
+ export declare function bytesEqual(a: Uint8Array, b: Uint8Array): boolean;
package/lib/hex.js ADDED
@@ -0,0 +1,33 @@
1
+ /** Small hex helpers (lowercase, no `0x`). */
2
+ export function toHex(bytes) {
3
+ let out = '';
4
+ for (const b of bytes) {
5
+ out += b.toString(16).padStart(2, '0');
6
+ }
7
+ return out;
8
+ }
9
+ export function fromHex(hex) {
10
+ let h = hex.startsWith('0x') || hex.startsWith('0X') ? hex.slice(2) : hex;
11
+ if (h.length % 2 !== 0) {
12
+ throw new Error(`Invalid hex length: ${hex}`);
13
+ }
14
+ const out = new Uint8Array(h.length / 2);
15
+ for (let i = 0; i < out.length; i++) {
16
+ const byte = Number.parseInt(h.slice(i * 2, i * 2 + 2), 16);
17
+ if (Number.isNaN(byte)) {
18
+ throw new Error(`Invalid hex: ${hex}`);
19
+ }
20
+ out[i] = byte;
21
+ }
22
+ return out;
23
+ }
24
+ export function bytesEqual(a, b) {
25
+ if (a.length !== b.length) {
26
+ return false;
27
+ }
28
+ let diff = 0;
29
+ for (let i = 0; i < a.length; i++) {
30
+ diff |= a[i] ^ b[i];
31
+ }
32
+ return diff === 0;
33
+ }
@@ -0,0 +1,14 @@
1
+ import type { ChainFamily } from '@unicitylabs/bridge-core';
2
+ /**
3
+ * Deterministic 32-byte Unicity TokenType for a bridged asset, from its source
4
+ * chain family (CAIP-2 namespace), chain id and asset contract:
5
+ * `SHA256("unicity-bridge:<family>:<chainId>:<assetEvmHex>")` (interop §2).
6
+ */
7
+ export declare function deriveTokenType(family: ChainFamily, chainId: number, assetContract: string): Uint8Array;
8
+ /** Deterministic 32-byte Sphere coinId for a bridged asset, same inputs as {deriveTokenType}. */
9
+ export declare function deriveCoinId(family: ChainFamily, chainId: number, assetContract: string): Uint8Array;
10
+ /**
11
+ * Commitment the Tron lock must carry so the bridged token can only be owned by
12
+ * the intended recipient: `SHA256(recipient.toCBOR())`.
13
+ */
14
+ export declare function recipientCommitment(recipientCbor: Uint8Array): Uint8Array;
@@ -0,0 +1,22 @@
1
+ import { sha256 } from '@noble/hashes/sha2.js';
2
+ import { toEvmAddressHex } from './address.js';
3
+ const utf8 = (s) => new TextEncoder().encode(s);
4
+ /**
5
+ * Deterministic 32-byte Unicity TokenType for a bridged asset, from its source
6
+ * chain family (CAIP-2 namespace), chain id and asset contract:
7
+ * `SHA256("unicity-bridge:<family>:<chainId>:<assetEvmHex>")` (interop §2).
8
+ */
9
+ export function deriveTokenType(family, chainId, assetContract) {
10
+ return sha256(utf8(`unicity-bridge:${family}:${chainId}:${toEvmAddressHex(assetContract)}`));
11
+ }
12
+ /** Deterministic 32-byte Sphere coinId for a bridged asset, same inputs as {deriveTokenType}. */
13
+ export function deriveCoinId(family, chainId, assetContract) {
14
+ return sha256(utf8(`unicity-bridge-coin:${family}:${chainId}:${toEvmAddressHex(assetContract)}`));
15
+ }
16
+ /**
17
+ * Commitment the Tron lock must carry so the bridged token can only be owned by
18
+ * the intended recipient: `SHA256(recipient.toCBOR())`.
19
+ */
20
+ export function recipientCommitment(recipientCbor) {
21
+ return sha256(recipientCbor);
22
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ import { type BridgeAssetConfig } from './config.js';
2
+ import { LockMintJustificationVerifier, type ResolvedBridgeConfig } from './LockMintJustificationVerifier.js';
3
+ import type { SourceChainRpc } from './source-chain.js';
4
+ import type { BridgedAmountExtractor } from './value.js';
5
+ export * from './address.js';
6
+ export * from './bridge-back/index.js';
7
+ export * from './BridgeLockJustification.js';
8
+ export * from './BridgeMintJustificationVerifier.js';
9
+ export * from './config.js';
10
+ export * from './contract-call.js';
11
+ export * from './evm/config.js';
12
+ export * from './evm/EvmRpcClient.js';
13
+ export * from './evm/family.js';
14
+ export * from './families.js';
15
+ export * from './hex.js';
16
+ export * from './identifiers.js';
17
+ export * from './lock-event.js';
18
+ export * from './LockMintJustificationVerifier.js';
19
+ export * from './source-chain.js';
20
+ export * from './tron/config.js';
21
+ export * from './tron/family.js';
22
+ export * from './tron/TronRpcClient.js';
23
+ export * from './value.js';
24
+ export interface CreateBridgePluginDeps {
25
+ /** Inject a node client (e.g. a mock). If omitted, the family builds one from `config.rpcUrl`. */
26
+ readonly rpc?: SourceChainRpc;
27
+ /** Override the token-value extractor. Defaults to the wallet value format (src/value.ts). */
28
+ readonly extractAmount?: BridgedAmountExtractor;
29
+ }
30
+ /** A ready-to-register bridge plugin for one bridged asset. */
31
+ export interface BridgePlugin {
32
+ readonly cborTag: bigint;
33
+ /** 32-byte Unicity TokenType (hex) for this asset. */
34
+ readonly tokenTypeHex: string;
35
+ /** 32-byte Sphere coinId (hex) for this asset. */
36
+ readonly coinIdHex: string;
37
+ readonly decimals: number;
38
+ readonly resolvedConfig: ResolvedBridgeConfig;
39
+ /** Register this into a MintJustificationVerifierService. */
40
+ readonly verifier: LockMintJustificationVerifier;
41
+ readonly rpc: SourceChainRpc;
42
+ }
43
+ /**
44
+ * Build a bridge plugin from canonical config. Derives the asset's
45
+ * TokenType/coinId, normalizes trust-anchor addresses through the chain
46
+ * family, and wires the verifier to the family's node client.
47
+ */
48
+ export declare function createBridgePlugin(config: BridgeAssetConfig, deps?: CreateBridgePluginDeps): BridgePlugin;
package/lib/index.js ADDED
@@ -0,0 +1,55 @@
1
+ import { DEFAULT_DECIMALS } from './config.js';
2
+ import { BRIDGE_LOCK_JUSTIFICATION_TAG } from './BridgeLockJustification.js';
3
+ import { chainFamily } from './families.js';
4
+ import { toHex } from './hex.js';
5
+ import { deriveCoinId, deriveTokenType } from './identifiers.js';
6
+ import { LockMintJustificationVerifier } from './LockMintJustificationVerifier.js';
7
+ export * from './address.js';
8
+ export * from './bridge-back/index.js';
9
+ export * from './BridgeLockJustification.js';
10
+ export * from './BridgeMintJustificationVerifier.js';
11
+ export * from './config.js';
12
+ export * from './contract-call.js';
13
+ export * from './evm/config.js';
14
+ export * from './evm/EvmRpcClient.js';
15
+ export * from './evm/family.js';
16
+ export * from './families.js';
17
+ export * from './hex.js';
18
+ export * from './identifiers.js';
19
+ export * from './lock-event.js';
20
+ export * from './LockMintJustificationVerifier.js';
21
+ export * from './source-chain.js';
22
+ export * from './tron/config.js';
23
+ export * from './tron/family.js';
24
+ export * from './tron/TronRpcClient.js';
25
+ export * from './value.js';
26
+ /**
27
+ * Build a bridge plugin from canonical config. Derives the asset's
28
+ * TokenType/coinId, normalizes trust-anchor addresses through the chain
29
+ * family, and wires the verifier to the family's node client.
30
+ */
31
+ export function createBridgePlugin(config, deps = {}) {
32
+ const family = chainFamily(config.family);
33
+ const tokenType = deriveTokenType(config.family, config.chainId, config.assetContract);
34
+ const coinId = deriveCoinId(config.family, config.chainId, config.assetContract);
35
+ const resolvedConfig = {
36
+ family: config.family,
37
+ chainId: config.chainId,
38
+ lockContractHex: family.normalizeAddress(config.lockContract),
39
+ assetContractHex: family.normalizeAddress(config.assetContract),
40
+ confirmations: config.confirmations ?? family.defaultConfirmations,
41
+ tokenType,
42
+ coinId,
43
+ };
44
+ const rpc = deps.rpc ?? family.createRpc({ rpcUrl: config.rpcUrl, apiKey: config.apiKey });
45
+ const verifier = new LockMintJustificationVerifier(resolvedConfig, { rpc, extractAmount: deps.extractAmount });
46
+ return {
47
+ cborTag: BRIDGE_LOCK_JUSTIFICATION_TAG,
48
+ tokenTypeHex: toHex(tokenType),
49
+ coinIdHex: toHex(coinId),
50
+ decimals: config.decimals ?? DEFAULT_DECIMALS,
51
+ resolvedConfig,
52
+ verifier,
53
+ rpc,
54
+ };
55
+ }
@@ -0,0 +1,28 @@
1
+ import type { SourceLog } from './source-chain.js';
2
+ /** Solidity signature of the UnicityLock `Lock` event. */
3
+ export declare const LOCK_EVENT_SIGNATURE = "Lock(uint256,address,uint256,bytes32,bytes32)";
4
+ /** topic0 of the Lock event = keccak256(signature), lowercase hex (no 0x). */
5
+ export declare const LOCK_EVENT_TOPIC0: string;
6
+ /** Decoded fields of a `Lock(nonce, from, amount, unicityTokenId, recipientCommitment)` event. */
7
+ export interface DecodedLockEvent {
8
+ readonly nonce: bigint;
9
+ /** 20-byte EVM-form address that called lock(), lowercase hex. */
10
+ readonly from: string;
11
+ readonly amount: bigint;
12
+ /** 32-byte Unicity tokenId the deposit is bound to. */
13
+ readonly unicityTokenId: Uint8Array;
14
+ /** 32-byte recipient commitment the deposit is bound to. */
15
+ readonly recipientCommitment: Uint8Array;
16
+ }
17
+ /**
18
+ * Decode a Lock event log. Returns null if the log is not a well-formed Lock
19
+ * event (wrong topic0, wrong arity, or short data) — callers treat that as
20
+ * "no matching lock event".
21
+ *
22
+ * Event layout (indexed nonce, indexed from; non-indexed in data):
23
+ * topics[0] = keccak256(signature)
24
+ * topics[1] = nonce (uint256)
25
+ * topics[2] = from (address, left-padded to 32 bytes)
26
+ * data = amount ‖ unicityTokenId ‖ recipientCommitment (3 × 32 bytes)
27
+ */
28
+ export declare function decodeLockEvent(log: SourceLog): DecodedLockEvent | null;
@@ -0,0 +1,48 @@
1
+ import { keccak_256 } from '@noble/hashes/sha3.js';
2
+ import { fromHex, toHex } from './hex.js';
3
+ /** Solidity signature of the UnicityLock `Lock` event. */
4
+ export const LOCK_EVENT_SIGNATURE = 'Lock(uint256,address,uint256,bytes32,bytes32)';
5
+ const utf8 = (s) => new TextEncoder().encode(s);
6
+ /** topic0 of the Lock event = keccak256(signature), lowercase hex (no 0x). */
7
+ export const LOCK_EVENT_TOPIC0 = toHex(keccak_256(utf8(LOCK_EVENT_SIGNATURE)));
8
+ function strip0x(h) {
9
+ return h.startsWith('0x') || h.startsWith('0X') ? h.slice(2) : h;
10
+ }
11
+ function wordToBigInt(word) {
12
+ let v = 0n;
13
+ for (const b of word) {
14
+ v = (v << 8n) | BigInt(b);
15
+ }
16
+ return v;
17
+ }
18
+ /**
19
+ * Decode a Lock event log. Returns null if the log is not a well-formed Lock
20
+ * event (wrong topic0, wrong arity, or short data) — callers treat that as
21
+ * "no matching lock event".
22
+ *
23
+ * Event layout (indexed nonce, indexed from; non-indexed in data):
24
+ * topics[0] = keccak256(signature)
25
+ * topics[1] = nonce (uint256)
26
+ * topics[2] = from (address, left-padded to 32 bytes)
27
+ * data = amount ‖ unicityTokenId ‖ recipientCommitment (3 × 32 bytes)
28
+ */
29
+ export function decodeLockEvent(log) {
30
+ const topics = log.topics.map(strip0x).map((t) => t.toLowerCase());
31
+ if (topics.length !== 3 || topics[0] !== LOCK_EVENT_TOPIC0) {
32
+ return null;
33
+ }
34
+ const data = fromHex(strip0x(log.data));
35
+ if (data.length < 96) {
36
+ return null;
37
+ }
38
+ const nonce = wordToBigInt(fromHex(topics[1]));
39
+ // address occupies the low 20 bytes of the 32-byte topic word.
40
+ const from = topics[2].slice(24);
41
+ return {
42
+ nonce,
43
+ from,
44
+ amount: wordToBigInt(data.subarray(0, 32)),
45
+ unicityTokenId: data.subarray(32, 64),
46
+ recipientCommitment: data.subarray(64, 96),
47
+ };
48
+ }
@@ -0,0 +1,29 @@
1
+ export interface SourceLog {
2
+ readonly address: string;
3
+ /** Indexed topics (topic0 = event signature hash). */
4
+ readonly topics: string[];
5
+ /** ABI-encoded non-indexed args. */
6
+ readonly data: string;
7
+ }
8
+ export interface SourceTxInfo {
9
+ readonly blockNumber: bigint;
10
+ readonly success: boolean;
11
+ readonly logs: SourceLog[];
12
+ }
13
+ export interface ConstantCallInput {
14
+ readonly ownerHex: string;
15
+ readonly contractHex: string;
16
+ /** Solidity function signature, e.g. `allowance(address,address)`. */
17
+ readonly functionSignature: string;
18
+ /** ABI-encoded arguments, hex (no `0x`); empty for no-arg calls. */
19
+ readonly parameterHex?: string;
20
+ }
21
+ export interface SourceChainRpc {
22
+ /** Returns null when the transaction is unknown to the node. */
23
+ getTransactionInfo(txidHex: string): Promise<SourceTxInfo | null>;
24
+ /** Current chain tip block number. */
25
+ getNowBlockNumber(): Promise<bigint>;
26
+ }
27
+ export interface ConstantCaller {
28
+ constantCall(input: ConstantCallInput): Promise<string>;
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Tron full-node HTTP API client (plain JSON, no tronweb dependency) behind the
3
+ * family-neutral {SourceChainRpc} and {ConstantCaller}.
4
+ */
5
+ import type { ConstantCallInput, ConstantCaller, SourceChainRpc, SourceTxInfo } from '../source-chain.js';
6
+ type FetchLike = (input: string, init?: {
7
+ method: string;
8
+ headers: Record<string, string>;
9
+ body: string;
10
+ }) => Promise<{
11
+ ok: boolean;
12
+ status: number;
13
+ json: () => Promise<unknown>;
14
+ }>;
15
+ export interface TronHttpRpcClientOptions {
16
+ /** Base URL, e.g. https://api.trongrid.io or https://nile.trongrid.io */
17
+ readonly baseUrl: string;
18
+ /** TronGrid API key (optional; sent as TRON-PRO-API-KEY). */
19
+ readonly apiKey?: string;
20
+ /** Injectable fetch (defaults to globalThis.fetch). */
21
+ readonly fetchFn?: FetchLike;
22
+ }
23
+ export declare class TronHttpRpcClient implements SourceChainRpc, ConstantCaller {
24
+ private readonly baseUrl;
25
+ private readonly apiKey?;
26
+ private readonly fetchFn;
27
+ constructor(options: TronHttpRpcClientOptions);
28
+ private post;
29
+ getTransactionInfo(txidHex: string): Promise<SourceTxInfo | null>;
30
+ getNowBlockNumber(): Promise<bigint>;
31
+ /**
32
+ * Read-only (constant) contract call — used for `allowance(owner, spender)` so
33
+ * bridge-in can skip a redundant `approve`. Addresses go over the wire in the
34
+ * hex `41…` form (visible:false). Throws if the node reports the call reverted
35
+ * or returns no result word.
36
+ */
37
+ constantCall(input: ConstantCallInput): Promise<string>;
38
+ }
39
+ export {};