Skip to content

Conversation

@spencerstock
Copy link
Collaborator

@spencerstock spencerstock commented Oct 16, 2025

What changed? Why?

Implements the Compressed RPC Link Format (Prolink) specification for encoding wallet RPC requests into compact, URL-safe payloads suitable for QR codes, NFC tags, and deep links.

Matches the spec defined here: ethereum/ERCs#1261

Core Implementation

  • Protocol Buffers v3 serialization for binary message encoding
  • Optional Brotli compression layer (quality 5, window 22 bits)
  • Base64url encoding without padding for URL safety
  • Three standardized shortcuts for optimized encoding

Shortcuts

Shortcut 0 (Generic JSON-RPC): Universal fallback for any RPC method
Shortcut 1 (wallet_sendCalls): Optimized for EIP-5792 with specialized encodings for:

  • ERC20 transfers (60-70% size reduction)
  • Native transfers
  • Generic call batches

Shortcut 2 (wallet_sign): Optimized for EIP-7871 with specialized encodings for:

  • SpendPermission signatures
  • ReceiveWithAuthorization signatures
  • Generic EIP-712 typed data

Features

  • Canonical encodings for addresses (20 bytes, lowercase normalized)
  • Minimal big-endian encoding for amounts (no leading zeros)
  • Capabilities extension point for metadata (ERC-8026 dataCallback support)
  • Protocol versioning and backward compatibility
  • Error handling with validation and rejection of malformed data

How was this tested?

Test coverage for:

  • End-to-end encoding/decoding roundtrips for all shortcuts
  • Base64url encoding/decoding with edge cases (padding, invalid characters)
  • Amount encoding (zero values, minimal encoding, leading zero rejection)
  • Address encoding (length validation, normalization)
  • ERC20 transfer detection (selector, data length, value validation)
  • Native transfer detection (empty data, non-zero value)
  • Brotli compression (Node.js and browser environments)
  • Protocol versioning and error cases
  • Capabilities encoding/decoding (JSON values as UTF-8 bytes)

How can reviewers manually test these changes?

  1. Navigate to the prolink playground page at /prolink-playground
  2. Test ERC20 transfer encoding:
    • Enter USDC contract, recipient, and amount
    • Click "Generate Prolink"
    • Click "Decode" to verify roundtrip accuracy
  3. Test native transfer encoding:
    • Switch to native transfer tab
    • Enter recipient and ETH amount
    • Verify encoding/decoding roundtrip
  4. Test wallet_sign encoding:
    • Switch to SpendPermission tab
    • Fill in permission parameters
    • Verify signature request encoding
  5. Compare payload sizes: check that compressed payloads are 60-80% smaller than raw JSON

Demo/screenshots

Screen.Recording.2025-10-17.at.2.37.10.PM.mov

@cb-heimdall
Copy link
Collaborator

cb-heimdall commented Oct 16, 2025

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

Comment on lines 43 to 46
export { decodeProlink, encodeProlink } from './interface/public-utilities/prolink/index.js';
export type { ProlinkDecoded, ProlinkRequest } from './interface/public-utilities/prolink/index.js';

export { showProlinkDialog } from './ui/ProlinkDialog/index.js';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new exports

Copy link
Collaborator Author

@spencerstock spencerstock Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed showProlinkDialog

@spencerstock spencerstock marked this pull request as ready for review October 21, 2025 02:10
@cb-jake
Copy link
Collaborator

cb-jake commented Oct 21, 2025

Whats the prompt to store this in the account sdk? This feels like a separate package that account sdk could likely consume?

*/
export function hexToBytes(hex: string): Uint8Array {
const normalized = hex.toLowerCase().replace(/^0x/, '');
const bytes = new Uint8Array(normalized.length / 2);
Copy link

@jxom jxom Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If hex is "0x123", then normalized.length = 3, and bytes = new Uint8Array(1.5) becomes new Uint8Array(1), so we will lose data here.

I would recommend using Bytes, Base64, etc from Ox for all these utilities fwiw since they are battle-tested and more performant.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, odd length hex is broken here. Adding test cases for coverage then applying a fix.

@spencerstock
Copy link
Collaborator Author

Whats the prompt to store this in the account sdk? This feels like a separate package that account sdk could likely consume?

Dm'd you the context here

@spencerstock spencerstock requested a review from jxom October 21, 2025 21:11
Comment on lines 12 to 28
webpack: (config, { isServer }) => {
// Exclude Node.js built-in modules from client bundle
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
zlib: false,
};

// Ignore node:zlib imports in client-side code
config.plugins.push(
new webpack.IgnorePlugin({
resourceRegExp: /^node:zlib$/,
})
);
}
return config;
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assuming consumers of our SDK don't have to do this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to Implement environment specific exports (and removed this)

*/
function detectSpendPermission(typedData: TypedData): boolean {
return (
typedData.primaryType === 'SpendPermission' && typedData.domain.verifyingContract !== undefined
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we not be checking for permission manager contract here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. Another implementation of a permission manager contract could still be valid in this context

Comment on lines 16 to 21
// Use browser's built-in btoa for base64 encoding
let binary = '';
for (let i = 0; i < data.length; i++) {
binary += String.fromCharCode(data[i]);
}
const base64 = btoa(binary);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: any reason not to use const base64String = Buffer.from(originalString).toString('base64'); here? afaik btoa is deprecated

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think btoa is standard, and Buffer.from is not a native web api

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 Buffer is not available in web

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this used in the playground anywhere to see what it looks like?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this for now - we may add it later if requested by devs

@spencerstock spencerstock force-pushed the spencer/prolink-implementation branch from 5a363ab to 6aac8bf Compare October 22, 2025 04:20
@spencerstock spencerstock force-pushed the spencer/prolink-implementation branch from 6aac8bf to 026315d Compare October 22, 2025 04:21
@cb-jake
Copy link
Collaborator

cb-jake commented Oct 22, 2025

@spencerstock, I'm not convinced this belongs in the SDK. I could see this being a separate package that the core SDK can consume. Would be curious if any others feel the same or differently

@spencerstock
Copy link
Collaborator Author

@spencerstock, I'm not convinced this belongs in the SDK. I could see this being a separate package that the core SDK can consume. Would be curious if any others feel the same or differently

I think the answer remains to be seen depending on what the protocol does and the types of investments we put into it in the future. But one sentiment Wilson is aligned with is that devs want more atomic control of what is displayed instead of delegating control under the hood. I think in the future where this becomes a more well-rounded solution to present requests it belongs in the base account package. It's ambiguous for now though and I'd default to just putting it in with the other utils.

fan-zhang-sv
fan-zhang-sv previously approved these changes Oct 22, 2025
Copy link
Collaborator

@fan-zhang-sv fan-zhang-sv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree looking from this PR this doesn't seem to be the best place for prolink

but from the sync w builder team this morning, sounds like

  • this is not final, and is for unblocking devs in this interim period before Bruno's standalone lib is live
  • we may migrate to import from standalone lib and convert this into a branded flow (?)

stamping to unblock, let's do quick iteration on our offering

@spencerstock spencerstock force-pushed the spencer/prolink-implementation branch from 3ba2525 to 48f4fcf Compare October 23, 2025 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants