Skip to content

About

Build the payment URI a wallet understands: EIP-681, BIP-21, Solana Pay. Zero dependencies, one file, no network calls. Extracted from the DarkRoute order page, where the amount rides in the QR so nobody has to retype it. Returns null rather than guessing: a payment needing a memo gets no URI, and neither does a chain whose id you cannot name.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@darkroute/payment-uri

Build the payment URI a wallet understands. EIP-681, BIP-21, Solana Pay. Zero dependencies, no network calls, about 90 lines.

This is what DarkRoute puts in the QR on an order page, extracted because the interesting part is not the encoder, it is knowing when to refuse.

import { payUri } from "@darkroute/payment-uri";

// native ETH: the value rides in the URI, in wei
payUri({ chain: "eth", chainId: 1, address: "0x1111…1111", amount: 0.1,
         amountUnits: "100000000000000000", contractAddress: null, memo: null });
// { uri: "ethereum:0x1111…1111@1?value=100000000000000000", scheme: "ethereum", exact: true }

// a token: a transfer() call on the token, not a send to the token
payUri({ chain: "base", chainId: 8453, address: "0x1111…1111", amount: 25,
         amountUnits: "25000000", contractAddress: "0x8335…2913", memo: null });
// { uri: "ethereum:0x8335…2913@8453/transfer?address=0x1111…1111&uint256=25000000", … }

// bitcoin: whole coins, never base units
payUri({ chain: "btc", chainId: null, address: "bc1q…", amount: 0.00412, … });
// { uri: "bitcoin:bc1q…?amount=0.00412", scheme: "bitcoin", exact: true }

The two refusals

It returns null rather than something plausible in two cases. Both are the point of the library.

A payment that needs a memo gets no URI. On TON, XRP, Cosmos chains and most exchange deposits, a transfer without its destination tag is a lost transfer, not a late one. Wallet support for carrying that tag inside a payment URI is uneven enough that you would be trusting somebody else's parser with your user's money. Show the address and the tag separately and let them paste both.

A chain you cannot name gets no URI. The chain id is an argument, not a lookup, so this library never has an opinion about which network your "eth" means. Pass chainId: null and you get null back, because a guessed chain id sends funds to the right address on the wrong network.

A URI that is subtly wrong is worse than no URI at all. The person scans it, the wallet fills in something plausible, and the mistake is only visible after the money has moved.

What it does not do

  • No QR rendering. It returns a string. Feed it to whichever QR library you already have.
  • No validation of the address itself. Shape only, and only enough to avoid emitting garbage. For real per-chain address checks see @darkroute/address-rules.
  • No SPL tokens. Solana Pay needs the mint address, and most registries hand you an asset id instead, so the token case is left alone rather than half guessed.
  • No amount maths. You pass both the decimal amount and the base-unit string; converting between them is your rounding decision, not ours.

Install

npm i @darkroute/payment-uri

Or copy src/index.ts. It is one file with no imports, and that is deliberate.

Test

npm test

Eight tests, node --test, no framework. The refusals have more coverage than the encoder.

Why this exists

DarkRoute quotes a swap, prints the fee before you deposit, and hands you a one-time deposit address. Retyping the amount from that page is where short deposits came from, and a short deposit comes back as a refund minus network cost. Putting the amount in the QR removed the retyping. The refusals above are what stops that convenience from becoming a new way to lose a deposit.

MIT.

About

Build the payment URI a wallet understands: EIP-681, BIP-21, Solana Pay. Zero dependencies, one file, no network calls. Extracted from the DarkRoute order page, where the amount rides in the QR so nobody has to retype it. Returns null rather than guessing: a payment needing a memo gets no URI, and neither does a chain whose id you cannot name.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages