@@ -42,6 +42,9 @@ import {
4242 OutputTokenAmountTooSmallError ,
4343 TokensToNonTokenAddressError ,
4444 UndefinedInputError ,
45+ OutputAddressNetworkMismatchError ,
46+ OutputTokenCategoryInvalidError ,
47+ OutputTokenCommitmentInvalidError ,
4548} from './Errors.js' ;
4649
4750// ////////// PARAMETER VALIDATION ////////////////////////////////////////////
@@ -51,25 +54,46 @@ export function validateInput(utxo: Utxo): void {
5154 }
5255}
5356
54- export function validateOutput ( output : Output ) : void {
55- if ( typeof output . to !== 'string' ) return ;
57+ export function validateOutput ( output : Output , network : Network ) : void {
58+ if ( isOpReturnOutput ( output ) ) return ;
5659
5760 const minimumAmount = calculateDust ( output ) ;
5861 if ( output . amount < minimumAmount ) {
5962 throw new OutputSatoshisTooSmallError ( output . amount , BigInt ( minimumAmount ) ) ;
6063 }
6164
6265 if ( output . token ) {
63- if ( ! isTokenAddress ( output . to ) ) {
64- throw new TokensToNonTokenAddressError ( output . to ) ;
65- }
66-
6766 if ( output . token . amount < 0n ) {
6867 throw new OutputTokenAmountTooSmallError ( output . token . amount ) ;
6968 }
69+
70+ if ( typeof output . token . category !== 'string' || ! isHex ( output . token . category ) ) {
71+ throw new OutputTokenCategoryInvalidError ( output . token . category ) ;
72+ }
73+
74+ if ( output . token . nft && ( typeof output . token . nft . commitment !== 'string' || ! isHex ( output . token . nft . commitment ) ) ) {
75+ throw new OutputTokenCommitmentInvalidError ( output . token . nft . commitment ) ;
76+ }
77+ }
78+
79+ // If the output is not an address (so it is P2S), then we don't need to do any address validation
80+ if ( typeof output . to !== 'string' ) return ;
81+
82+ if ( output . token && ! isTokenAddress ( output . to ) ) {
83+ throw new TokensToNonTokenAddressError ( output . to ) ;
84+ }
85+
86+ const addressPrefix = getNetworkPrefixForAddress ( output . to ) ;
87+ const networkPrefix = getNetworkPrefix ( network ) ;
88+ if ( addressPrefix !== networkPrefix ) {
89+ throw new OutputAddressNetworkMismatchError ( output . to , networkPrefix ) ;
7090 }
7191}
7292
93+ export function isOpReturnOutput ( output : Output ) : boolean {
94+ return typeof output . to !== 'string' && output . to [ 0 ] === Op . OP_RETURN ;
95+ }
96+
7397export function calculateDust ( output : Output ) : number {
7498 const outputSize = getOutputSize ( output ) ;
7599 // Formula used to calculate the minimum allowed output
@@ -87,16 +111,6 @@ export function encodeOutput(output: Output): Uint8Array {
87111}
88112
89113export function cashScriptOutputToLibauthOutput ( output : Output ) : LibauthOutput {
90- if ( output . token ) {
91- if ( typeof output . token . category !== 'string' || ! isHex ( output . token . category ) ) {
92- throw new Error ( `Provided token category ${ output . token ?. category } is not a hex string` ) ;
93- }
94-
95- if ( output . token . nft && ( typeof output . token . nft . commitment !== 'string' || ! isHex ( output . token . nft . commitment ) ) ) {
96- throw new Error ( `Provided token commitment ${ output . token . nft ?. commitment } is not a hex string` ) ;
97- }
98- }
99-
100114 return {
101115 lockingBytecode : typeof output . to === 'string' ? addressToLockScript ( output . to ) : output . to ,
102116 valueSatoshis : output . amount ,
@@ -150,6 +164,12 @@ function isTokenAddress(address: string): boolean {
150164 return supportsTokens ;
151165}
152166
167+ function getNetworkPrefixForAddress ( address : string ) : string {
168+ const result = decodeCashAddress ( address ) ;
169+ if ( typeof result === 'string' ) throw new Error ( result ) ;
170+ return result . prefix ;
171+ }
172+
153173// ////////// SIZE CALCULATIONS ///////////////////////////////////////////////
154174export function getInputSize ( inputScript : Uint8Array ) : number {
155175 const scriptSize = inputScript . byteLength ;
0 commit comments