@@ -8,6 +8,41 @@ import { constFalse, constTrue } from "effect/Function"
8
8
import * as Chain from "./schema/chain.js"
9
9
import * as Hex from "./schema/hex.js"
10
10
import * as Utils from "./Utils.js"
11
+ import { isValidSuiAddress , normalizeSuiAddress } from "@mysten/sui/utils"
12
+
13
+ /**
14
+ * @category schemas
15
+ * @since 2.0.0
16
+ */
17
+ export class SuiCoin extends S . TaggedClass < SuiCoin > ( ) ( "SuiCoin" , {
18
+ address : S . String . pipe (
19
+ S . filter ( ( value ) => {
20
+ const parts = value . split ( "::" )
21
+ if ( parts . length !== 3 ) return false
22
+ const [ addr , module , name ] = parts
23
+
24
+ const ident = / ^ [ A - Z a - z _ ] [ A - Z a - z 0 - 9 _ ] * $ /
25
+ if ( ! ident . test ( module ) || ! ident . test ( name ) ) return false
26
+
27
+ const norm = normalizeSuiAddress ( addr )
28
+ return isValidSuiAddress ( norm )
29
+ } , {
30
+ description :
31
+ "Sui coin type in the form 0x<hex>::<Module>::<Name> with a valid Sui address and Move identifiers" ,
32
+ } ) ,
33
+ S . annotations ( {
34
+ examples : [
35
+ "0x2::sui::SUI" ,
36
+ "0x9003c05db750fe8fb33d8e9a7de814b2ca1af024dc67e06f8529260b03d86fdd::usdt_faucet::USDT_FAUCET" ,
37
+ ] ,
38
+ } ) ,
39
+ ) ,
40
+ } ) {
41
+ [ Hash . symbol ] ( ) : number {
42
+ return Hash . string ( this . address )
43
+ }
44
+ }
45
+
11
46
12
47
/**
13
48
* @category schemas
@@ -132,6 +167,7 @@ export const Any = S.Union(
132
167
CosmosTokenFactory ,
133
168
CosmosBank ,
134
169
CosmosIbcClassic ,
170
+ SuiCoin ,
135
171
)
136
172
/**
137
173
* @category models
@@ -154,6 +190,7 @@ export const TokenFromString = S.transformOrFail(
154
190
S . decodeEither ( CosmosIbcClassic ) ( { _tag : "CosmosIbcClassic" , address } ) ,
155
191
S . decodeEither ( CosmosTokenFactory ) ( { _tag : "CosmosTokenFactory" , address } ) ,
156
192
S . decodeEither ( Cw20 ) ( { _tag : "Cw20" , address } ) ,
193
+ S . decodeEither ( SuiCoin ) ( { _tag : "SuiCoin" , address } ) ,
157
194
] ) ,
158
195
Effect . orElse ( ( ) => S . decodeEither ( Erc20 ) ( { _tag : "Erc20" , address } ) ) ,
159
196
Effect . orElse ( ( ) => S . decodeEither ( CosmosBank ) ( { _tag : "CosmosBank" , address } ) ) ,
@@ -184,6 +221,8 @@ export const AnyFromEncoded = (rpcType: Chain.RpcType) =>
184
221
) ) ,
185
222
Match . when ( "aptos" , ( fromA ) =>
186
223
Effect . fail ( new ParseResult . Type ( ast , fromA , "Aptos not supported." ) ) ) ,
224
+ Match . when ( "sui" , ( ) =>
225
+ pipe ( fromA , S . decode ( S . compose ( Hex . StringFromHex , TokenFromString ) ) ) ) ,
187
226
Match . exhaustive ,
188
227
Effect . catchTag ( "ParseError" , ( error ) =>
189
228
ParseResult . fail ( error . issue ) ) ,
@@ -196,6 +235,17 @@ export const AnyFromEncoded = (rpcType: Chain.RpcType) =>
196
235
} ,
197
236
)
198
237
238
+ export const normalizeSuiTypeTag = ( t : string ) : string => {
239
+ const [ addr , mod , name ] = t . split ( "::" )
240
+ return `${ normalizeSuiAddress ( addr ) } ::${ mod } ::${ name } `
241
+ }
242
+
243
+ const isNativeSui = ( t : string ) : boolean => {
244
+ // compare on normalized address to avoid short/long mismatch
245
+ const norm = normalizeSuiTypeTag ( t )
246
+ return norm === "0x2::sui::SUI"
247
+ }
248
+
199
249
/**
200
250
* @category predicates
201
251
* @since 2.0.0
@@ -208,5 +258,6 @@ export const isNative = Match.type<Any>().pipe(
208
258
Cw20 : constFalse ,
209
259
Erc20 : constFalse ,
210
260
EvmGas : constTrue ,
261
+ SuiCoin : ( t ) => isNativeSui ( t . address ) ,
211
262
} ) ,
212
263
)
0 commit comments