Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions NIP-002.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
```
NIP: 2
Layer: Applications
Title: Payment URI Scheme
Authors: codewordneptune
Status: Draft
```

# Motivation

There is currently no documented standard for expressing "pay this address this amount" as a link or QR code. Every wallet and site that wants payments must invent its own format, and none of them would interoperate. This NIP (Neptune Improvement Proposal), Neptune Cash's equivalent of Bitcoin's [BIP 21](https://en.bitcoin.it/wiki/BIP_0021), unblocks several things at once:

- QR-based receive/send flows in wallets
- Click-to-pay links on explorers, donation pages, and merchant sites
- Deep-linking (`neptunecash:` opens the wallet with a pre-filled send screen)

# Existing practice

The two existing wallets already use an undocumented `NPT:` prefix in QR codes, but only for bare addresses, with no additional metadata:

- [neptune-wallet-app](https://github.com/Neptune-Crypto/neptune-wallet-app) generates receive QR payloads as `NPT:<ADDRESS>` (uppercased).
- [npt-mobile-wallet](https://github.com/zeokin/npt-mobile-wallet) displays the same format on the receive screen, and its scanner strips a leading `npt:` case-insensitively and treats the remainder as the address.

No amount, label, or other parameters exist today, and nothing registers an OS-level URI handler. This proposal documents that practice and defines its successor, without breaking anything already deployed: existing address-only `NPT:` payloads keep working in the wallets that already accept them, and the parameterized URIs this NIP introduces fail safe when scanned into a current wallet (the query string makes bech32m decoding fail, producing an invalid-address error rather than a payment with wrong details).

# Specification

```
neptunecash:<address>[?amount=<amount>][&label=<label>][&message=<message>]
```

- The scheme is `neptunecash:`, matching the project's full name; see open question 2 for the naming rationale. The address-only `NPT:` payload described under existing practice is a wallet-specific convention that was never standardized; wallets MAY keep accepting it for compatibility with deployed QR codes, but this spec does not require it.
- Each query parameter is optional and may appear in any order; the query string, if present, is introduced by `?` and parameters are separated by `&`, as in ordinary URIs.
- `<address>` is a bech32m receiving address as produced by neptune-core, any addressable key type. The network is already encoded in the address's human-readable part (HRP), the prefix before the `1` separator, such as `nolgam` for a mainnet generation address, so the URI needs no separate network field; wallets MUST reject a URI whose address network does not match the active network.
- Scheme matching is case-insensitive. The address may be all-uppercase or all-lowercase (all-uppercase enables the smaller QR alphanumeric mode, which is why the wallets uppercase today); mixed case is invalid per bech32m.
- Parameter names are lowercase and case-sensitive; only the address may be uppercased, never the whole URI. Uppercasing a parameterized URI would also gain nothing: QR alphanumeric mode has no `?`, `=`, or `&` in its character set, so the smaller encoding only ever benefits address-only payloads.
- `amount` is optional: a decimal string in whole NPT units (e.g. `amount=1.25`). No exponent notation, `.` as separator.
- `label` is optional: percent-encoded UTF-8, a name for the recipient (shown in the wallet UI, stored in contacts/history).
- `message` is optional: percent-encoded UTF-8, a note for the payer's own records.
- Both `label` and `message` are local metadata for the payer's wallet. **Neither is transmitted on-chain**, nor sent to the recipient in any form.
- Unknown query parameters MUST be ignored (forward compatibility). The exception is the reserved prefix `req-` (for "required"): a parameter prefixed `req-` that the wallet does not understand MUST cause the wallet to treat the entire URI as invalid, same convention as BIP 21. This gives future extensions a way to mark parameters that are unsafe to ignore, such as a hypothetical `req-expires`.

Example:

```
neptunecash:nolgam1abc...xyz?amount=10&label=Dev%20Fund&message=August%20pledge
```

### Test vectors (provisional)

These vectors are hand-derived; they will be re-verified against the reference parser implementation, and extended, before this NIP leaves Draft status. In the vectors, `<ADDR>` stands for the following known-valid mainnet generation address, taken from neptune-core's test suite:

```
nolgam1cvqtx45kkfqhkmzt74ec98laulywqevxyyqhhchs5v5g07t76uqjnp4la3htxa5jev004wsmtt0w0ktxc08qz03ztpx80t652qgvh44k7s3esxg6flkdnhmljlx9vwz4zt3awn2lj0y3pga5yj7rusp8mtp7alkzkt0fvufxwmqwqyxzg5z8d54a8zj7l0az237yc6wjxr6z04d2skdql82p5qaks7crqwafgu7nm3yjkwjnetrhlqp8u0y22vgavdltgdhc6xql0q7krh6f8rn9snuyr5whufy5pk2ja5mhhkal6v2mt0f4wj9y27fhep60cxgv4gnlyu9j36yava459dr9zzm9ctq32juar4dmpsgt98sq0twnzhvfkmyfvvjtjtdfm9t9yfkexyf5und9xfj3ll9qxy4qdvzz3mft0kj5kw0cggacy7p4f0zuag3pxexh20378k2gr0kt7nkmchaum8ud80a4t697hfj67p6mamu00d8g2erx4fhuu9xfuzv8sjgwqzkfjdkaayrfqy5kvrkmpf27z2r5pyd783ghwzgqk8z96ek0xjxxawem849nn24r80u42m3s63y6zqlsfu3jyy3ddq2rzjn0pj7fyt3f7k2smum9mzhwzw03vfh8lcetrcgtdjc83ctcre4ajmhlkt8s3r2wgnf43d4lndkml20yu7z3xua3ev3nudtq0mt0mx4rvakml08qy8nln08t6dew6v9q46xp94e3s83sf6as8v5w27uvyqvckymqg7mpuhht2gmxrug0tj4uz6hsymatwhaq0m25p4hkptfft4jcgly9l26ufcu23f3knj53u5tfv3gkm3rdzruhjra0k9d6g3xhmvpsu3n780y65m5vx22a3pfyvzenv0zf8te55f4gmhhtqwy57zhazsd3wqkcf0qrd7vpndeprssgu4hc9373ytapkersgufemdvselrkf7fdl3xh7sx4ph42pjxcgtx6vyqqjedeqgtsfev57eefpc9uje6lkvwnzxh5dvmwk8w69ynkmnza0qppnkgzd8m7fnthxrml20v2uce8hl655tfppsgfgnyc80q2mnpmx6f5jkvlmyhdsvd56ywzahr68qs37s8edegwad9u90fw44774yshp9k8kyj9am8hmuqczg0capa38gh3jupsgu3heaxkrlfhwcghl3k8s72dg9ljf76c6f79v5x4jpykvacdkhc7wp02g5je6su8nyqr7zef9z9sqlwwazc8u3cu8s2usum77qmj47eh8w424sued8x8lc7mxj398f6gt8yph0zgrslserjdpmvf877sum79k0nq39haw904eruls6ed0zkpkmw7twj7p67wz6ewhc9nu6dnackmjsugmxsphvqgxwll54fr5v4cy23xsyadtnd5u5p62qwyqz6sv8clgjcyhaekf4sul8zlxmc4p7h0uf75s9929343wn769z7vy0vaj4sfq4544s9zmnxxlyuquxamq94qnwl5nvsqxumfwl3jdef8lxfgreacsv78k89vqfllx7sknx5lr3jj4upynjf5cxlmjdcn8e6rlryrc8ecju70dg98tsuj0h7reqt0j7uagdqk2sqe77eftlrwqmap9ceuwe60n7akkntc2r5pttshv9mpsykhy44mam6k09azgma3m78ktkqr8zm35l5z9vhn0mczfu9sfpsztdehl2wlt4h3dy0lrjvrqf6r69xe05r9ut8x9n87fuvy7ny6crpfdvzfxekzye3pefhr4k8v6th2hjkz3ekxy6r3h4dvzqq7hns0qz0xr5w27xrejvjgaw08m9pmrlzns9uk4tt9ykus34mf6nxfdvgw5pyklnvzj3xnzjessely3u3v6gp2ncfmct47yu507pc5r7uear28llzz8krd8e0zcrlmhvjj69q75ttm9kn6emkqxt2acux63ezjc66hs6pnynrffgffdl6hdns0e0nkz58t5j7h6wltje7hfvzwzxzmshsxvpsgj3flzlptzfwv34udx5g3g4kpcpu04p85xvdq0m0qmkmpg88uv6jsts6u4m2al45gr2tzdg6fuaedwlvhkf9neav0r264eadtvgnk8ahgshfaluqwqz604s73hzp9qj8en397h3lhl6d5a2xz5w2jujak2l62p35c55h07plx4jxz5dwwed6v8ym682dl00eqml7zxewma6dhhl9ufp0rw83ckuw9qft92y7l7t4fqzfumglhcqpkrgl0vjpzkzhda734mauxv6lr2glcjee0xp96ls3rnsl0l5e2qexx4aj2rhllezphmqzka047pafa6l7k2lr75xgyc4p84dvskldmcljm67jgjw4nxawnqternspgph4793fthad5yp3zvt5vtcmcr36sygjkw89zu0vewcnu9gfx3u7wpwvwz585d0dl5ycg0dq4l2z74cyznn5cmz43qj8745duqgrpmx23vhjzhmzmck8mq37h65duxx547z6ml52mmtnkc0x2hh2tm83t6s37l0aau8v2kw88v4gx3jfmez2re626lgydhz5je47zh8rlc74qxk83pr87nwcvhtlh2jjn8ksv38hc8g5sh4gtx9emj7q8kzsgjqvsn0l8rnhzx0ztseqr3usuztva7ejkj7degxgyur2w92n4m7mudufslz9d5puuzd6yzk3g4rjyphr6sun0v28qffumdupx737udpeextntdnkrl8u8h7kgntwj7eh8ynvwqcuer5xne36nmhmr3awv7kyfc7xh9g4sl4xj9zg5fzmehjda6d9zwcnkwfhcar2gh84jmew47e69uak2s329x6mg7sn23dkswyla7xjfn5r23a7uj7r3r5jt4djg8m7en5p6vh290jajs5fpm4hkfza4uwp29nqtn9wf05hlvgjnx9fjh3pzlhd97s3ge3xczchl306ayqunlu2kyasklt8lu0mt6dy734wdtgxpu2s5jw3m8jrkm5epz6s9mf03z6xsf4efaahzamxpdvvx68ykqjpeu3y0mps2sj4z86ucxd2tnwtgks00ep5hmrnvcjwmfqkgdfp3x7x3zx9r3tc2fz70mzpal64hd5ewalxvkxmv8uz6rt48vjqfghfe49d5derqdjege6g477esdkgnmpmpj5uhx9vxcm48lqqpucnn7d2xap00twgje3927m80jnuvagw2mfa4j9vcsl9mauzjn7ps9naa5kkg6d2l4yv4f0f6u88er0st25sq65p4zppukfrjknl8nkr4ujgrgk8tulatqh93ydd7hexrlsh9zje0eskp8656zwx25fs53ee6atrknffj95a85mfwtdsd5djafr3rs2c4wvurfeslj6s8869wvlj0yw7a730npk2dhgwgmq4p9ag0eugcuu5ucsycul295xcwprr
```

`<ADDR-UPPER>` stands for the same address converted to all-uppercase.

Valid, a conforming wallet accepts these:

- `neptunecash:<ADDR>` parses as the bare address, no parameters.
- `neptunecash:<ADDR>?amount=1.25` parses with an amount of 1.25 NPT.
- `NEPTUNECASH:<ADDR>?amount=10&label=Dev%20Fund` parses with an amount of 10 NPT and the label `Dev Fund`; scheme matching is case-insensitive.
- `neptunecash:<ADDR-UPPER>?amount=1.25` parses; an all-uppercase address is accepted.
- `neptunecash:<ADDR>?futuredata=abc&amount=2` parses with an amount of 2 NPT; the unknown parameter `futuredata` is ignored, and parameter order does not matter.
- `neptunecash:<ADDR>?AMOUNT=10` parses as the bare address with **no** amount: parameter names are case-sensitive, so `AMOUNT` is an unknown parameter and is ignored.

Invalid, a conforming wallet rejects these:

- `neptunecash:<ADDR>?req-expires=2027-01-01`, because the wallet does not recognize the `req-` prefixed parameter.
- `neptunecash:<ADDR>?amount=1,25`, because the decimal separator must be `.`.
- `neptunecash:<ADDR>?amount=1e3`, because exponent notation is not allowed.
- `neptunecash:<ADDR>` with the final character `r` replaced by `q`, because the bech32m checksum fails.
- `neptunecash:<ADDR>` with the first letter of the address uppercased, because mixed case is invalid per bech32m.
- The first valid vector, presented to a wallet whose active network is not mainnet, because the address HRP `nolgam` does not match the active network.

# Open questions

### 1. QR capacity vs. address length

Generation addresses are long enough to strain practical QR code density. Options considered: recommend the shorter address types for QR use where acceptable; accept large (version 25+) QR codes; or additionally spec an out-of-band form where the URI carries a URL from which the full payment request is fetched (BIP72-style).

**Tentatively resolved** in the [RFC discussion](https://github.com/Neptune-Crypto/neptune-core/issues/959): recommend the shorter address types for QR use, and where feasible also accept the large QR codes needed to encode generation addresses. Fetchable payment requests stay out of scope for v1 and can be revisited later; in their place, the spec will include QR size guidance.

### 2. Scheme name

The wallets' existing QR payloads use `NPT:`, and extending that payload is an option, but a fully spelled-out scheme name is preferable: other cryptocurrencies consistently use their project name (`bitcoin:`, `zcash:`, `monero:`), not their ticker.

A bare `neptune:` was the initial proposal, but the project name is "Neptune Cash", not "Neptune", and the name "Neptune" is contested: [Neptune Privacy](https://github.com/neptuneprivacy/xnt-core) (XNT) is a live fork that also presents itself as Neptune. This is not a funds-safety concern, because the two chains use distinct bech32m HRPs (`nolga` plus a network character in neptune-core, `xntnw` plus a network character in xnt-core), so a foreign address simply fails to decode, which is the fail-safe behavior this proposal already relies on. The collision is narrower but real: OS-level URI handler registration is a single global namespace per scheme, so if both projects ship wallets registering `neptune:`, the handler goes to whichever was installed last, and a user with both installed gets the wrong app from a click-to-pay link.

`neptunecash:` is therefore the more faithful application of the project-name convention, not a departure from it, and the table below already records the precedent: Bitcoin Cash uses `bitcoincash:`, not `bitcoin:`. The cost is 4 extra characters in the QR payload, negligible next to a generation address of roughly 3500 characters and irrelevant for links.

**Proposal**: register/document `neptunecash:` as primary for links and deep-linking; leave support for the legacy address-only `npt:` payload as an optional compatibility choice for the wallets that already ship it, since it is a wallet-specific convention this spec does not standardize.

### 3. Amount denomination

Whole NPT with a decimal fraction, or the smallest unit?

**Proposal**: whole NPT, which matches BIP 21 practice and human expectations. The table below records the cost of the alternative: smallest-unit amounts, as in Ethereum's wei and Nano's raw, are a recurring source of decimal-shift bugs.

# Payment URI schemes in other cryptocurrencies

| Coin | Scheme | Spec | Parameters | Notes |
|---|---|---|---|---|
| Bitcoin | `bitcoin:` | [BIP 21](https://en.bitcoin.it/wiki/BIP_0021) | `amount`, `label`, `message`, `req-*` | The template most others copy. [BIP 72](https://github.com/bitcoin/bips/blob/master/bip-0072.mediawiki) added `?r=<url>` for fetchable payment requests (BIP 70, now abandoned). |
| Zcash | `zcash:` | [ZIP 321](https://zips.z.cash/zip-0321) | `amount`, `label`, `message`, `memo` | Closest precedent for Neptune Cash: a privacy coin with long/multiple address types. Supports multiple recipients in one URI via indexed params (`address.1=`, `amount.1=`). |
| Monero | `monero:` | de facto wallet convention | `tx_amount`, `recipient_name`, `tx_description` | Same idea as BIP 21 but with its own parameter names: the fragmentation a written spec avoids. |
| Ethereum | `ethereum:` | [EIP-681](https://eips.ethereum.org/EIPS/eip-681) | `value`, `gas`, chain id, function calls | Extends into contract invocation; `value` is in wei (smallest unit). |
| Litecoin / Dogecoin / Dash | `litecoin:` etc. | BIP 21 clones | same as Bitcoin | Fork keeps the convention, changes the scheme name. |
| Bitcoin Cash | `bitcoincash:` | CashAddr + BIP 21 params | `amount`, `label`, `message` | The address format itself carries the prefix, so bare address and URI are the same string. Precedent for open question 2: the fork spells out the full project name rather than claiming the parent chain's `bitcoin:`. |
| Solana | `solana:` | [Solana Pay](https://docs.solanapay.com/spec) | `amount`, `spl-token`, `reference`, `label`, `message`, `memo` | `reference` is a unique key letting merchants detect the payment on-chain (the tracking feature [neptune-wallet-app#66](https://github.com/Neptune-Crypto/neptune-wallet-app/issues/66) asks for). |
| Stellar | `web+stellar:` | [SEP-0007](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0007.md) | `destination`, `amount`, `memo`, callback | Uses the `web+` prefix convention for browser handler registration. |
| Nano | `nano:` | de facto | `amount` (in raw, smallest unit) | Smallest-unit amounts are a frequent source of decimal-shift bugs. |