@laceio/sdk 0.1.1-canary.3 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{TransactionBuilder-CCjOn7NA.cjs → TransactionBuilder-DrB3NVTi.cjs} +0 -42
- package/dist/{TransactionBuilder-CIzdw18M.js → TransactionBuilder-V6aLx4SV.js} +1 -1
- package/dist/{_polyfill-buffer-patch-CM8X7JiY.cjs → _polyfill-buffer-patch-BnPVerlG.cjs} +2 -21
- package/dist/account-guards.stable-B1DSSh9D.cjs +10 -0
- package/dist/account-guards.stable-JRQ5qx2V.js +5 -0
- package/dist/{activity-type-Ch6ljxBy.cjs → activity-type.stable-BJEfeLxL.cjs} +1 -2
- package/dist/{activity-type-GsJGWs-3.js → activity-type.stable-C76_hWd7.js} +1 -2
- package/dist/{address-book-address-validator-0J9XAADH.js → address-book-address-validator-DIUolI-s.js} +2 -2
- package/dist/{address-book-address-validator-Dte_YQ2L.cjs → address-book-address-validator-DNwQGlOB.cjs} +2 -2
- package/dist/bitcoin.cjs +0 -45
- package/dist/bitcoin.d.ts +1 -29906
- package/dist/bitcoin.js +0 -20
- package/dist/{blockfrost-tx-submit-provider-CLNQ5ycR.cjs → blockfrost-tx-submit-provider-BAIzV5o3.cjs} +167 -3
- package/dist/{blockfrost-tx-submit-provider-CBWQ0wK9.js → blockfrost-tx-submit-provider-DPTrapz-.js} +157 -5
- package/dist/{secret-box-DrjLZuS1.cjs → bytes.vo-Ck5u40RA.cjs} +35 -1
- package/dist/{secret-box-DUR0ACYz.js → bytes.vo-TgiwogWF.js} +25 -3
- package/dist/{cardano-network-id.vo-Cfpvbod9.js → cardano-network-id.vo-CK1tNQDg.js} +1 -1
- package/dist/{cardano-network-id.vo-BEDPn6Li.cjs → cardano-network-id.vo-DSxrFk9S.cjs} +1 -1
- package/dist/cardano.cjs +18 -22
- package/dist/cardano.d.ts +3774 -18959
- package/dist/cardano.js +11 -16
- package/dist/codecs-B7X9pAsK.cjs +1945 -0
- package/dist/codecs-CC1Xjntz.js +1882 -0
- package/dist/{const-D-eZIS4n.cjs → const-BDEg2Trq.cjs} +4 -4
- package/dist/{const-iwzZEE_w.cjs → const-BY8B3_0_.cjs} +1 -1
- package/dist/{const-BJfIK_G-.js → const-C84qHkjp.js} +4 -4
- package/dist/{const-CjmfjBP3.js → const-DXk2tFyv.js} +1 -1
- package/dist/{delegation-tx-builder-DZFjHDS8.js → delegation-tx-builder-CGVqlaFE.js} +5 -5
- package/dist/{delegation-tx-builder-CCjiakoy.cjs → delegation-tx-builder-CRpLP6jH.cjs} +6 -6
- package/dist/{deregistration-tx-builder-Bf6xAYp4.js → deregistration-tx-builder-B-CrUyX6.js} +5 -5
- package/dist/{deregistration-tx-builder-CQP35yex.cjs → deregistration-tx-builder-DZGwB4cz.cjs} +6 -6
- package/dist/{earn-rewards-tx-builder-Bsga3yFb.js → earn-rewards-tx-builder-BDbTDZmD.js} +5 -5
- package/dist/{earn-rewards-tx-builder-GuxofYns.cjs → earn-rewards-tx-builder-D5sUT5v8.cjs} +6 -6
- package/dist/{group-cardano-addresses-by-account-Bp0g4wEY.cjs → group-cardano-addresses-by-account-B533oVXR.cjs} +39 -3
- package/dist/{group-cardano-addresses-by-account-SR8MMrZf.js → group-cardano-addresses-by-account-RHKoEskR.js} +35 -5
- package/dist/{guarded-transaction-signer-MFpPg98D.cjs → guarded-transaction-signer-Bxl6_XNd.cjs} +18 -17
- package/dist/{guarded-transaction-signer-P6Rp1faj.js → guarded-transaction-signer-CpKLj8Kp.js} +15 -8
- package/dist/{in-memory-wallet-integration-t9I00R_V.cjs → in-memory-wallet-integration-CnLYeX4-.cjs} +108 -17
- package/dist/{in-memory-wallet-integration-CljprQQV.js → in-memory-wallet-integration-Croiuopx.js} +104 -7
- package/dist/{in-memory-wallet-integration-ChIOirMW.js → in-memory-wallet-integration-D-qn65Kx.js} +2 -5
- package/dist/{in-memory-wallet-integration-Cm6P8sk6.cjs → in-memory-wallet-integration-DVh9sRXa.cjs} +2 -3
- package/dist/index.cjs +47 -84
- package/dist/index.d.ts +13480 -40888
- package/dist/index.js +24 -52
- package/dist/{init-DUF4ar8P.cjs → init-B64zCLxG.cjs} +4 -4
- package/dist/{init-Y3mkB4XS.js → init-BAJJzBza.js} +2 -2
- package/dist/{init-EhzD7a8i.cjs → init-BJVU54pI.cjs} +1 -1
- package/dist/{init-D6BxBs7e.cjs → init-BKhH6mVR.cjs} +1 -1
- package/dist/{init-EP0CUWNK.cjs → init-BsCBAGWf.cjs} +2 -2
- package/dist/{init-Dy-05ktr.cjs → init-BtzMZ7Pk.cjs} +1 -1
- package/dist/init-Bv53hpLM.js +19 -0
- package/dist/{init-BopV9ed4.cjs → init-C-jErzp5.cjs} +2 -2
- package/dist/{init-Bp9GsPS_.cjs → init-C4Bkib9u.cjs} +56 -46
- package/dist/{init-lzHt4Cli.cjs → init-C7IeJwQR.cjs} +59 -256
- package/dist/{init-C-G32J12.cjs → init-CB__aLD7.cjs} +2 -2
- package/dist/{init-CyAY4Lzl.js → init-CFYsq82V.js} +1 -1
- package/dist/{init-0zzs9aqU.js → init-CLHuAMB-.js} +4 -4
- package/dist/{init-DA7zmptx.js → init-CNLGJJRx.js} +1 -1
- package/dist/{init-DS5qN63m.js → init-CR6tpw1B.js} +32 -24
- package/dist/init-CRbjI7p1.js +3494 -0
- package/dist/{init-CSDpdu0Q.cjs → init-CSHwHnfg.cjs} +2 -2
- package/dist/{init-CNM5m7M2.js → init-Cg9XdAru.js} +4 -4
- package/dist/{init-B3-1ghyx.js → init-CigestoM.js} +2 -2
- package/dist/{init-Dv3zKj0r.cjs → init-D6DBUsD3.cjs} +4 -4
- package/dist/init-DAna1KCg.cjs +3495 -0
- package/dist/{init-D74mn8nH2.js → init-DDdhguVC.js} +2 -2
- package/dist/init-DGe47B22.cjs +364 -0
- package/dist/init-DMtyvYfC.cjs +21 -0
- package/dist/{init-BrdVzxei.cjs → init-DX4WyB0R.cjs} +3 -3
- package/dist/{init-C8URA2OX.js → init-DcYY6zMD.js} +1 -1
- package/dist/{init-DFMiP2ZH.cjs → init-DkIc5nqF.cjs} +1 -1
- package/dist/{init-kEfUTuG2.js → init-Dku4b2lG.js} +1 -1
- package/dist/{init-NdhG_VrF.cjs → init-DxUHsN53.cjs} +6 -6
- package/dist/{init-CfTbSEKy.js → init-FMGDET-9.js} +60 -259
- package/dist/{init-DrkOb-wd.cjs → init-LfyMo-sS.cjs} +2 -2
- package/dist/{init-NCjgwCZy.js → init-Rxohwpmu.js} +2 -2
- package/dist/init-TkPDD750.js +363 -0
- package/dist/{init-C2RgrfZe.js → init-TkPq_Ny_.js} +1 -1
- package/dist/{init-5KcGcBb5.cjs → init-U8c9yHYx.cjs} +1 -1
- package/dist/{init-CX0AMQUn.js → init-aJHl-7AO.js} +2 -2
- package/dist/{init-rmt7-fQE.cjs → init-anYu6XB7.cjs} +2 -2
- package/dist/{init-Fw89ZmrR.js → init-w9GEs73W.js} +2 -2
- package/dist/{init-Dnsv155v.js → init-y2Ra0vZP.js} +3 -3
- package/dist/{init-DFjT8YvT.js → init-zSzCjoor.js} +4 -4
- package/dist/{is-retriable-error-BUiVJDwH.js → is-retriable-error-BU0e3hVI.js} +1 -1
- package/dist/{is-retriable-error-Dpt860if.cjs → is-retriable-error-CeeuHqDa.cjs} +1 -1
- package/dist/{lace-context-BZciB9c7.js → lace-context-CZcNHr4M.js} +4 -4
- package/dist/{lace-context-DgR6vLli.cjs → lace-context-fJTDghdp.cjs} +4 -4
- package/dist/{libsodium-wrappers-BhbOA8dQ.js → libsodium-wrappers-UH0LaOUE.js} +1647 -3
- package/dist/modules-cardano-BDvuL4Xx.js +5248 -0
- package/dist/modules-cardano-D2G3De2P.cjs +5432 -0
- package/dist/{retry-config-Dmgt28wM.js → retry-config-BcUmMS--.js} +1 -1
- package/dist/{retry-config-BaCfVC1H.cjs → retry-config-DC5IMaH7.cjs} +1 -1
- package/dist/{seam-cardano-B_etH4F4.js → seam-cardano-CATqBEyK.js} +1 -1
- package/dist/{signer-factory-DObB55D4.js → signer-factory-9URFliA5.js} +3 -6
- package/dist/{signer-factory-Ds6G2AZk.cjs → signer-factory-BWGUmnvm.cjs} +3 -4
- package/dist/{slice-qt0uBExh.js → slice-B2PhoWAg.js} +37 -325
- package/dist/{slice-DyNs7oWp.js → slice-BNu73oBa.js} +2 -2
- package/dist/{slice-GwiiJRoq.js → slice-CB4SnzMm.js} +3 -3
- package/dist/slice-CGVHXHDt.cjs +125 -0
- package/dist/{slice-CU80pbiu.js → slice-CnyWevM9.js} +3 -3
- package/dist/{slice-CwyYrh1H.js → slice-CobanYgn.js} +2 -2
- package/dist/{slice-7G2xxYKJ.cjs → slice-CuHUaPZx.cjs} +3 -3
- package/dist/{slice-DayJvMRD.cjs → slice-D11m2EKl.cjs} +1 -1
- package/dist/{slice-Bg6HEr3K.cjs → slice-DGcNrbPR.cjs} +4 -4
- package/dist/{slice-B4CdYoPu.cjs → slice-DZmQJNV8.cjs} +2 -2
- package/dist/slice-DhvF7L6X.js +107 -0
- package/dist/{slice-C9TT5KhY.cjs → slice-Do2R2CYW.cjs} +6 -6
- package/dist/{slice-Bs2aMERu.cjs → slice-DtaO72qY.cjs} +3 -3
- package/dist/{slice-C34iOOUH.cjs → slice-HeHCv3bl.cjs} +7 -7
- package/dist/{slice-D-9aPKG-.js → slice-d0p6-xj5.js} +6 -6
- package/dist/{slice-CK07EBCo.js → slice-eIwcwKHI.js} +4 -4
- package/dist/{slice-DBpJ2QGf.js → slice-maaskdUn.js} +6 -6
- package/dist/{slice-DTZTiJle.cjs → slice-mxLQ_FeY.cjs} +39 -352
- package/dist/{slice-DnG-yvpI.cjs → slice-oqm_r1cS.cjs} +2 -2
- package/dist/{slice-Da426sf9.js → slice-zJ9XVYjZ.js} +1 -1
- package/dist/{sync-operation-utils-31iFLExW.js → sync-operation-utils-BYXLO80m.js} +38 -2
- package/dist/{sync-operation-utils-COIZ66-I.cjs → sync-operation-utils-SdPbwFoD.cjs} +67 -1
- package/dist/{token-id-mapper-D29Ggv8h.js → token-id-mapper-B2ivlLv3.js} +2 -2
- package/dist/{token-id-mapper-CVyQJUKY.cjs → token-id-mapper-DsPAL6Nh.cjs} +2 -2
- package/dist/{util-Cp5cZkfK.js → util-BaLzwa32.js} +1 -1
- package/dist/{util-Bu4W5lF4.cjs → util-CGzqQ8IV.cjs} +1 -1
- package/dist/{utils-DsElZgCI.cjs → utils-5JvjbqH3.cjs} +1 -7
- package/dist/{utils-BfBEUvWA.js → utils-o8aMjfln.js} +2 -2
- package/dist/{vote-delegation-tx-builder-BUg1S2Ep.cjs → vote-delegation-tx-builder-BpEMSti4.cjs} +6 -6
- package/dist/{vote-delegation-tx-builder-C6FiS7zI.js → vote-delegation-tx-builder-CmKv6GoG.js} +5 -5
- package/package.json +4 -96
- package/dist/_polyfill-buffer-patch-yNxN4d5E.js +0 -1467
- package/dist/_web-crypto-random-bytes-Bo-GTcRS.js +0 -197
- package/dist/acc-module-4iIUv6My.cjs +0 -27654
- package/dist/acc-module-Cmj8EsIb.js +0 -27624
- package/dist/account-BOQruszJ.js +0 -6
- package/dist/account-BUOj4pYJ.cjs +0 -11
- package/dist/account-call-D68oF6bh.js +0 -267
- package/dist/account-call-DIIa2rK_.cjs +0 -314
- package/dist/account-call-NgLhYein.cjs +0 -4
- package/dist/account-call-n-Nkp35e.js +0 -3
- package/dist/account-guards-BtjTGxDZ.cjs +0 -42
- package/dist/account-guards-D9Lu5T3W.js +0 -36
- package/dist/account-tokens-DvkOFVON.js +0 -16
- package/dist/account-tokens-nwcvwLu4.cjs +0 -21
- package/dist/address-B9ntKL8K.js +0 -13761
- package/dist/address-BXp-mY8H.cjs +0 -11270
- package/dist/address-book-address-validator-DK0dnsRS.cjs +0 -41
- package/dist/address-book-address-validator-Di_ywo8M.js +0 -39
- package/dist/artefact-loader-54uK5RAa.js +0 -160
- package/dist/artefact-loader-DZPYrq-6.cjs +0 -183
- package/dist/auto-dismiss-failure-on-success-C-qoJuid.cjs +0 -37
- package/dist/auto-dismiss-failure-on-success-s8MGDEIu.js +0 -31
- package/dist/bip32-D6wd_QMn.cjs +0 -3851
- package/dist/bitcoin-data-provider-BYPSmuZS.js +0 -21
- package/dist/bitcoin-data-provider-w1oEDSU3.cjs +0 -26
- package/dist/bn-d3nSOIf7.cjs +0 -2553
- package/dist/browser-B1eMbDwh.js +0 -7468
- package/dist/bytes.vo-BB7_4JK6.js +0 -23
- package/dist/bytes.vo-BNRquqaO.cjs +0 -34
- package/dist/cardano-DfPwhwm-.js +0 -3
- package/dist/client-DXBLHk7K.cjs +0 -808
- package/dist/codecs-Cswkg7Tm.cjs +0 -4051
- package/dist/codecs-Dc1Krpbt.js +0 -3903
- package/dist/const-BhYOdznB.cjs +0 -42
- package/dist/const-BsnjG2_Q.cjs +0 -32071
- package/dist/const-CUnCl5U0.js +0 -13
- package/dist/const-D0CbQlo4.js +0 -154
- package/dist/contract-BD_312b0.js +0 -171
- package/dist/contract-DevBp9u8.cjs +0 -189
- package/dist/create-add-accounts-Brqk84vF.js +0 -102
- package/dist/create-add-accounts-DzXIh7Ot.cjs +0 -114
- package/dist/device-commitment-hex.vo-5wFtQvDS.js +0 -26
- package/dist/device-commitment-hex.vo-CT3RmAug.cjs +0 -31
- package/dist/device-readers-70i7tQqU.cjs +0 -8
- package/dist/device-readers-B7--ezKM.js +0 -4
- package/dist/device-readers-BWOmz9eD.js +0 -145
- package/dist/device-readers-CIgYZN7f.cjs +0 -168
- package/dist/dist-BMi5ZjEq.cjs +0 -43175
- package/dist/dist-BtIq4XFn.cjs +0 -535
- package/dist/dist-C3SHC_Ho.cjs +0 -1394
- package/dist/dist-CykEjYEX.cjs +0 -3814
- package/dist/dist-Dcsxh3be.cjs +0 -3452
- package/dist/dist-DqJfSlTd.cjs +0 -4413
- package/dist/dist-nTZ70qj7.cjs +0 -1306
- package/dist/enc-key-circuits-C8fGAMEF.cjs +0 -24
- package/dist/enc-key-circuits-DA2Xs3vZ.js +0 -13
- package/dist/english-Byb9M0E_.cjs +0 -2057
- package/dist/envelopes-BkH1Qdxk.js +0 -1620
- package/dist/envelopes-ETZEJS_O.cjs +0 -1721
- package/dist/errors-BZ8hnhCM.cjs +0 -584
- package/dist/errors-DIJTAsLa.js +0 -392
- package/dist/esm-HBSUQw3w.js +0 -384
- package/dist/esm-LZuaZjMf.cjs +0 -389
- package/dist/experimental.cjs +0 -7075
- package/dist/experimental.d.ts +0 -85525
- package/dist/experimental.js +0 -6917
- package/dist/gated-call-Boy6rXdC.js +0 -3
- package/dist/gated-call-DJWY299W.cjs +0 -736
- package/dist/gated-call-DjoRPqn1.js +0 -695
- package/dist/gated-call-SomdJ5nj.cjs +0 -4
- package/dist/grant-circuits-_1x--moV.cjs +0 -49
- package/dist/grant-circuits-swuzEdUl.js +0 -26
- package/dist/grant-rules-7Zqi_3BN.cjs +0 -7114
- package/dist/grant-rules-_xJHAT2M.js +0 -6833
- package/dist/grant-status-B2y49iIu.js +0 -2688
- package/dist/grant-status-DOr3t_u8.cjs +0 -2820
- package/dist/grants--b0LQQzZ.cjs +0 -266
- package/dist/grants-BIs6qm3b.js +0 -213
- package/dist/grants-CSgfYlOO.js +0 -5
- package/dist/grants-D61131nK.cjs +0 -17
- package/dist/hex-D9ITYbwt.cjs +0 -263
- package/dist/hex-mkXcV3pN.js +0 -191
- package/dist/http-client-B3N-TMxu.cjs +0 -167
- package/dist/http-client-TRlhDZGh.js +0 -155
- package/dist/identity-BMLuufva.cjs +0 -263
- package/dist/identity-DII6f-h7.js +0 -233
- package/dist/in-memory-wallet-integration-B2xzxORj.cjs +0 -20741
- package/dist/in-memory-wallet-integration-CBRiPjLO.js +0 -130
- package/dist/in-memory-wallet-integration-CULUB5Tf.cjs +0 -240
- package/dist/in-memory-wallet-integration-TM9bBphA.js +0 -20738
- package/dist/init-8v_n8boO.js +0 -1177
- package/dist/init-B5X_sGj9.cjs +0 -6
- package/dist/init-B8U3ABhS.cjs +0 -107
- package/dist/init-BJn-8_hf.js +0 -141
- package/dist/init-BmzfDaKL.js +0 -1142
- package/dist/init-BpHTq2R5.js +0 -105
- package/dist/init-BpSITBfF.js +0 -39
- package/dist/init-C00gw1zi.js +0 -422
- package/dist/init-CR7BfztQ.cjs +0 -18
- package/dist/init-Cp8EJGbm.js +0 -5
- package/dist/init-CuW7rHNA.js +0 -3584
- package/dist/init-Cz0rz5fU.cjs +0 -3585
- package/dist/init-D8OeObDF.cjs +0 -1141
- package/dist/init-D9g1HXYf.cjs +0 -40
- package/dist/init-DGjXwWs2.cjs +0 -143
- package/dist/init-DHrCbpkW.cjs +0 -121
- package/dist/init-DU5yYX5u.js +0 -2374
- package/dist/init-DlqJuHhl.js +0 -119
- package/dist/init-DrRdf5sZ.cjs +0 -1176
- package/dist/init-DuYENxci.js +0 -17
- package/dist/init-GsT2AEa4.cjs +0 -424
- package/dist/init-ZjNDDC-T.cjs +0 -2375
- package/dist/jubjub-authoriser-BxE3oN2y.js +0 -86
- package/dist/jubjub-authoriser-C6SDEP9I.cjs +0 -87
- package/dist/key-source-5oUBFiC7.cjs +0 -583
- package/dist/key-source-B08HukZg.js +0 -2550
- package/dist/keyDerivation-D3k9XyHg.js +0 -4077
- package/dist/keyDerivation-u0nBA93T.cjs +0 -274
- package/dist/lib--iqvW4O_.js +0 -2354
- package/dist/lib-Yk6Upl5D.cjs +0 -2359
- package/dist/midnight-account-id.vo-0x9X-r3l.cjs +0 -10
- package/dist/midnight-account-id.vo-DjCAGcqc.js +0 -5
- package/dist/midnight-engine-DpemplKO.js +0 -5
- package/dist/midnight-engine-xU1wotne.cjs +0 -6
- package/dist/midnight-wallet-DIsAlSn6.js +0 -18
- package/dist/midnight-wallet-XTZc2li7.cjs +0 -24
- package/dist/midnight.cjs +0 -1408
- package/dist/midnight.d.ts +0 -28934
- package/dist/midnight.js +0 -1352
- package/dist/modules-cardano-BrS68BbA.js +0 -995
- package/dist/modules-cardano-D843LvB0.cjs +0 -1108
- package/dist/node-relay-Cv0cNknM.js +0 -48
- package/dist/node-relay-DblH5fKv.cjs +0 -49
- package/dist/outputs-Bd2yZVjx.js +0 -720
- package/dist/outputs-CJoQBIA2.cjs +0 -990
- package/dist/pbkdf2-CIW-oWrm.cjs +0 -1337
- package/dist/pending-operation-B0_P78Jo.cjs +0 -26
- package/dist/pending-operation-BIS6mpjB.js +0 -21
- package/dist/providers-BLgJ0uQs.cjs +0 -366
- package/dist/providers-CwnT4vEE.js +0 -343
- package/dist/redacted-BmE2aXqr.js +0 -81
- package/dist/redacted-CRZJLS3w.cjs +0 -86
- package/dist/safe-buffer-B_vMqJWU.js +0 -43
- package/dist/safe-buffer-DYOwEfHB.cjs +0 -48
- package/dist/sdk-Dd0MKXFz.cjs +0 -1439
- package/dist/sdk-_yO75IHD.js +0 -1241
- package/dist/sdk-init-CYViTJY_.cjs +0 -3841
- package/dist/sdk-init-OMHyOGOK.js +0 -4138
- package/dist/send-proof-progress-C-ljq1x5.js +0 -30
- package/dist/send-proof-progress-vmsaSfbz.cjs +0 -47
- package/dist/side-effects-BnqYz7Nl.js +0 -768
- package/dist/side-effects-CngaeXi0.cjs +0 -804
- package/dist/sign-bitcoin-transaction-with-seed-D4z62-jV.js +0 -147
- package/dist/sign-bitcoin-transaction-with-seed-DsLYXnCY.cjs +0 -158
- package/dist/signer-3fk8lZtE.cjs +0 -7
- package/dist/signer-CsL28DrK.cjs +0 -121
- package/dist/signer-DGluibtn.js +0 -92
- package/dist/signer-RqB5pu3K.js +0 -2
- package/dist/signer-factory-BA7WFJVZ.js +0 -282
- package/dist/signer-factory-Cwd6o-lG.cjs +0 -281
- package/dist/signer-factory-DhZiE03y.cjs +0 -149
- package/dist/signer-factory-hzSRaIw2.js +0 -148
- package/dist/slice-BPNuHKfw.js +0 -9091
- package/dist/slice-CxKWDuBP.js +0 -87
- package/dist/slice-Czfp6t84.cjs +0 -436
- package/dist/slice-DnBWfA-k.cjs +0 -9109
- package/dist/slice-HjLXJyQG2.cjs +0 -105
- package/dist/slice-oWnc7YVD.js +0 -412
- package/dist/src-BDzYIksE.cjs +0 -20637
- package/dist/src-BFoefPv_.js +0 -20755
- package/dist/store-B1Lq3cEf.cjs +0 -66
- package/dist/store-DuBG8qh5.js +0 -37
- package/dist/submit-BBYjnZu8.cjs +0 -87
- package/dist/submit-D_yxYM_T.js +0 -70
- package/dist/submit-bitcoin-tx-BG3Gemov.js +0 -983
- package/dist/submit-bitcoin-tx-Y0CtvFN7.cjs +0 -1049
- package/dist/token-id-mapper-Bnds3xKH.cjs +0 -18
- package/dist/token-id-mapper-CnKHQ8DV.js +0 -16
- package/dist/tokens-Cevp7rdU.cjs +0 -62
- package/dist/tokens-Diw4MLRM.js +0 -44
- package/dist/types-B8uiJn-X.js +0 -12
- package/dist/types-CJT6XF2c.js +0 -10
- package/dist/types-CLRUYpfe.cjs +0 -23
- package/dist/types-DWbVZpnF.cjs +0 -15
- package/dist/types-T0cjaR75.js +0 -33
- package/dist/types-Zj78LtYu.cjs +0 -50
- package/dist/unwrap-provider-result-BZSiwabU.js +0 -32
- package/dist/unwrap-provider-result-ZqfBsTkk.cjs +0 -39
- package/dist/utils-DFwzEvbv.js +0 -65
- package/dist/utils-IXxR-Djh.cjs +0 -112
- package/dist/v8Signatures-CO_Uloy7.cjs +0 -71
- package/dist/watch-D-RoItM5.js +0 -21997
- package/dist/watch-DMF2s-II.cjs +0 -52281
- /package/dist/{account-id.vo-DwqFjK5u.cjs → account-id.vo-LvCBVn5r.cjs} +0 -0
- /package/dist/{account-id.vo-OPz2v-JZ.js → account-id.vo-MYSI7YFm.js} +0 -0
- /package/dist/{actions-iLXdgL5Y.js → actions-9QSQR-GB.js} +0 -0
- /package/dist/{actions-B2BJ4RTy.cjs → actions-DdGbj7c7.cjs} +0 -0
- /package/dist/{apply-vkey-witnesses-ZnFS0jnJ.cjs → apply-vkey-witnesses-CLzVSWxl.cjs} +0 -0
- /package/dist/{apply-vkey-witnesses-BKx3lcs0.js → apply-vkey-witnesses-ClZbpk5w.js} +0 -0
- /package/dist/{asyncoption-CruEYOBI.js → asyncoption-Cw07zPH7.js} +0 -0
- /package/dist/{asyncoption-i0VbqVTq.cjs → asyncoption-DYoGuQfP.cjs} +0 -0
- /package/dist/{bignum.vo-CEXOIew0.js → bignum.vo-B0rK_PQ1.js} +0 -0
- /package/dist/{bignum.vo-hx2kuqlA.cjs → bignum.vo-DsG3kNiZ.cjs} +0 -0
- /package/dist/{bip32ed25519-B7Yat217.cjs → bip32ed25519-1ApqeAZk.cjs} +0 -0
- /package/dist/{bip32ed25519-FQ8qvdcq.js → bip32ed25519-DetAdGwD.js} +0 -0
- /package/dist/{blake2b-DEeVUdKR.cjs → blake2b-B9tftbvG.cjs} +0 -0
- /package/dist/{blake2b-BiQTw3GC.js → blake2b-DOEVc57D.js} +0 -0
- /package/dist/{blockchain-network-id.vo-BAKEwGjG.cjs → blockchain-network-id.vo-BXpZhvvQ.cjs} +0 -0
- /package/dist/{blockchain-network-id.vo-CiVykuqG.js → blockchain-network-id.vo-DNRkuT5B.js} +0 -0
- /package/dist/{blockingWithLatestFrom-Cd8c8EPG.js → blockingWithLatestFrom-C5wp_uA-.js} +0 -0
- /package/dist/{blockingWithLatestFrom-D8kBPBMX.cjs → blockingWithLatestFrom-DY8OPg0w.cjs} +0 -0
- /package/dist/{cardano-network-id-to-network-type-C0ufJOyE.cjs → cardano-network-id-to-network-type-BDTF-HmK.cjs} +0 -0
- /package/dist/{cardano-network-id-to-network-type-BWwzeEtM.js → cardano-network-id-to-network-type-CrZfUcyP.js} +0 -0
- /package/dist/{cardano-tx-id.vo-TLjJZHCI.cjs → cardano-tx-id.vo-BPhV-AmZ.cjs} +0 -0
- /package/dist/{cardano-tx-id.vo-eVnKuzlm.js → cardano-tx-id.vo-DF9NzZXk.js} +0 -0
- /package/dist/{composite-signer-factory-m5cVs9h_.cjs → composite-signer-factory-BpF1mIYA.cjs} +0 -0
- /package/dist/{composite-signer-factory-CR8YHA1y.js → composite-signer-factory-_46QsNBz.js} +0 -0
- /package/dist/{error-DIZOY2Lw.js → error-BCp9NkPi.js} +0 -0
- /package/dist/{error-Cb3EnD0u.cjs → error-CNIquqT5.cjs} +0 -0
- /package/dist/{esm-browser-MG97cre9.js → esm-browser-C93Q-EOI.js} +0 -0
- /package/dist/{esm-browser-bweqfBlL.cjs → esm-browser-DTc9IK3D.cjs} +0 -0
- /package/dist/{feature-flag-key.vo-RlfHL3hq.cjs → feature-flag-key.vo-BPz-ZAp6.cjs} +0 -0
- /package/dist/{feature-flag-key.vo-DN_-4-sG.js → feature-flag-key.vo-ofrB6ZVQ.js} +0 -0
- /package/dist/{first-state-of-status-unPynOuM.js → first-state-of-status-DQnMT3ya.js} +0 -0
- /package/dist/{first-state-of-status-CbT7hPa4.cjs → first-state-of-status-OROkXcRv.cjs} +0 -0
- /package/dist/{init-zUoRG_EC2.cjs → init-C5CEZ1m0.cjs} +0 -0
- /package/dist/{init-DHaMxHM1.js → init-CKipPE_c.js} +0 -0
- /package/dist/{init-Atisw4x2.cjs → init-DFQQ9dTW.cjs} +0 -0
- /package/dist/{init-829HRYcz.js → init-Es3wHW7D.js} +0 -0
- /package/dist/{init-BgNobbNq.cjs → init-IAk_CkGa.cjs} +0 -0
- /package/dist/{init-atCFur0v.js → init-cqN3uX2j.js} +0 -0
- /package/dist/{is-redux-persist-state-BCFnSm36.cjs → is-redux-persist-state-CTn3WZVh.cjs} +0 -0
- /package/dist/{is-redux-persist-state-yBfq0h_E.js → is-redux-persist-state-Cxcd0_bM.js} +0 -0
- /package/dist/{key-value-store--W3SrMaw.js → key-value-store-8cMzeNyG.js} +0 -0
- /package/dist/{key-value-store-C2Xt5Emp.cjs → key-value-store-DUAepXQf.cjs} +0 -0
- /package/dist/{repo-slice-RxFm0zDZ.js → repo-slice-DwyMhCR1.js} +0 -0
- /package/dist/{repo-slice-7X4AYYVm.cjs → repo-slice-WxetRhMY.cjs} +0 -0
- /package/dist/{reselect-CGoLzEPv.js → reselect-C06X06oX.js} +0 -0
- /package/dist/{reselect-Bd94u0K-.cjs → reselect-CRDqmQY9.cjs} +0 -0
- /package/dist/{serializable-BHdKIgk3.js → serializable-B8GahgE1.js} +0 -0
- /package/dist/{serializable-BxHkoPdk.cjs → serializable-CRdnFaLZ.cjs} +0 -0
- /package/dist/{slice-obCSLm1x.js → slice-B87b52iM.js} +0 -0
- /package/dist/{slice-Bk0Sv05J.js → slice-Be7hj4Iy.js} +0 -0
- /package/dist/{slice-BiRsDKOK.cjs → slice-D11FHQuQ.cjs} +0 -0
- /package/dist/{slice-Dqw1ecum.cjs → slice-FioKeHtA.cjs} +0 -0
- /package/dist/{src-Cqp0B2M_.cjs → src-BTPIvlDI.cjs} +0 -0
- /package/dist/{src-BlxEp6Mm.js → src-vq1wS9sZ.js} +0 -0
- /package/dist/{time.vo-D3wWRw9L.cjs → time.vo-CQ8pSXz2.cjs} +0 -0
- /package/dist/{time.vo-tNheinXm.js → time.vo-DkYYGYp6.js} +0 -0
- /package/dist/{toEmpty-8Zax1MPd.js → toEmpty-CgAl3097.js} +0 -0
- /package/dist/{toEmpty-CYAbupM4.cjs → toEmpty-DAMmNB5r.cjs} +0 -0
- /package/dist/{token-id.vo-DXv5uS9n.cjs → token-id.vo-BxG9TG0O.cjs} +0 -0
- /package/dist/{token-id.vo-Bvwhr3ZE.js → token-id.vo-ClsroxHO.js} +0 -0
- /package/dist/{util-CzeB3hfa.cjs → util-CmdAhheh.cjs} +0 -0
- /package/dist/{util-C8a4fA9p.js → util-DxzlYNS5.js} +0 -0
- /package/dist/{util-selector-BWPH68_l.cjs → util-selector-BcAncU4L.cjs} +0 -0
- /package/dist/{util-selector-BSTsxgne.js → util-selector-CcK-GAdE.js} +0 -0
- /package/dist/{while-active-B0lWy_6l.js → while-active-BpppWuCs.js} +0 -0
- /package/dist/{while-active-Dsowp0Fi.cjs → while-active-Dhc2LwOm.cjs} +0 -0
|
@@ -1,2820 +0,0 @@
|
|
|
1
|
-
const require_rolldown_runtime = require("./rolldown-runtime-Br2OG8yv.cjs");
|
|
2
|
-
const require_hex = require("./hex-D9ITYbwt.cjs");
|
|
3
|
-
const require_outputs = require("./outputs-CJoQBIA2.cjs");
|
|
4
|
-
const require_tokens = require("./tokens-Cevp7rdU.cjs");
|
|
5
|
-
const require_identity = require("./identity-BMLuufva.cjs");
|
|
6
|
-
let _laceio_cardano_crypto = require("@laceio/cardano/crypto");
|
|
7
|
-
_laceio_cardano_crypto = require_rolldown_runtime.__toESM(_laceio_cardano_crypto);
|
|
8
|
-
let _laceio_cardano_core = require("@laceio/cardano/core");
|
|
9
|
-
let _laceio_cardano_util = require("@laceio/cardano/util");
|
|
10
|
-
let _laceio_cardano_tx_construction = require("@laceio/cardano/tx-construction");
|
|
11
|
-
//#region ../../packages/lib/cardano-custody/src/analysis/transaction-facts.ts
|
|
12
|
-
const outpointKey = ({ txId, index }) => `${txId.toLowerCase()}#${index}`;
|
|
13
|
-
const toTxIn = ({ txId, index }) => ({
|
|
14
|
-
txId,
|
|
15
|
-
index
|
|
16
|
-
});
|
|
17
|
-
/** Byte order of lower-case hex, then index: how the ledger orders inputs. */
|
|
18
|
-
const compareOutpoints = (a, b) => {
|
|
19
|
-
const aId = a.txId.toLowerCase();
|
|
20
|
-
const bId = b.txId.toLowerCase();
|
|
21
|
-
if (aId !== bId) return aId < bId ? -1 : 1;
|
|
22
|
-
return a.index - b.index;
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* Pairs every input the body names with the caller's resolution. Every
|
|
26
|
-
* input, reference input and collateral input must be resolved exactly once,
|
|
27
|
-
* and no resolution may name an outpoint the body does not.
|
|
28
|
-
*/
|
|
29
|
-
const resolveBody = (body, resolvedInputs) => {
|
|
30
|
-
const refusals = [];
|
|
31
|
-
const resolutions = /* @__PURE__ */ new Map();
|
|
32
|
-
const sources = [
|
|
33
|
-
[body.inputs, "inputs"],
|
|
34
|
-
[body.referenceInputs ?? [], "referenceInputs"],
|
|
35
|
-
[body.collaterals ?? [], "collaterals"]
|
|
36
|
-
];
|
|
37
|
-
const named = new Set(sources.flatMap(([inputs]) => inputs.map(outpointKey)));
|
|
38
|
-
for (const [input, txOut] of resolvedInputs) {
|
|
39
|
-
const key = outpointKey(input);
|
|
40
|
-
const view = require_outputs.viewOfOutput(txOut);
|
|
41
|
-
if (!named.has(key)) refusals.push({
|
|
42
|
-
code: "custody-context-mismatch",
|
|
43
|
-
reason: "resolution-not-in-body",
|
|
44
|
-
input: toTxIn(input)
|
|
45
|
-
});
|
|
46
|
-
else if (resolutions.has(key)) refusals.push({
|
|
47
|
-
code: "custody-context-mismatch",
|
|
48
|
-
reason: "duplicate-resolution",
|
|
49
|
-
input: toTxIn(input)
|
|
50
|
-
});
|
|
51
|
-
else if (view.address === void 0) refusals.push({
|
|
52
|
-
code: "custody-context-mismatch",
|
|
53
|
-
reason: "invalid-resolution",
|
|
54
|
-
input: toTxIn(input)
|
|
55
|
-
});
|
|
56
|
-
resolutions.set(key, view);
|
|
57
|
-
}
|
|
58
|
-
const resolve = (inputs, source) => inputs.flatMap((input) => {
|
|
59
|
-
const output = resolutions.get(outpointKey(input));
|
|
60
|
-
if (output) return [{
|
|
61
|
-
input: toTxIn(input),
|
|
62
|
-
output
|
|
63
|
-
}];
|
|
64
|
-
refusals.push({
|
|
65
|
-
code: "custody-context-mismatch",
|
|
66
|
-
reason: "unresolved-input",
|
|
67
|
-
input: toTxIn(input),
|
|
68
|
-
source
|
|
69
|
-
});
|
|
70
|
-
return [];
|
|
71
|
-
});
|
|
72
|
-
const [inputs, referenceInputs, collaterals] = sources.map(([list, source]) => resolve(list, source));
|
|
73
|
-
return refusals.length > 0 ? require_hex.fail(refusals) : require_hex.ok({
|
|
74
|
-
inputs: inputs.sort((a, b) => compareOutpoints(a.input, b.input)),
|
|
75
|
-
referenceInputs,
|
|
76
|
-
collaterals
|
|
77
|
-
});
|
|
78
|
-
};
|
|
79
|
-
/** The key of the withdrawal map in a transaction body. */
|
|
80
|
-
const WITHDRAWALS_KEY = 5n;
|
|
81
|
-
/**
|
|
82
|
-
* The reward accounts the withdrawal maps of a body name, read from its
|
|
83
|
-
* bytes, repeats included: a repeated key, and a repeated map, are lost
|
|
84
|
-
* once the body is decoded.
|
|
85
|
-
*/
|
|
86
|
-
const withdrawalAccountsOf = (bodyCbor) => {
|
|
87
|
-
const reader = new _laceio_cardano_core.Serialization.CborReader(bodyCbor);
|
|
88
|
-
const accounts = [];
|
|
89
|
-
reader.readStartMap();
|
|
90
|
-
while (reader.peekState() !== _laceio_cardano_core.Serialization.CborReaderState.EndMap) {
|
|
91
|
-
if (reader.readInt() !== WITHDRAWALS_KEY) {
|
|
92
|
-
reader.skipValue();
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
reader.readStartMap();
|
|
96
|
-
while (reader.peekState() !== _laceio_cardano_core.Serialization.CborReaderState.EndMap) {
|
|
97
|
-
accounts.push(_laceio_cardano_core.Cardano.Address.fromBytes(_laceio_cardano_util.HexBlob.fromBytes(reader.readByteString())).toBech32());
|
|
98
|
-
reader.skipValue();
|
|
99
|
-
}
|
|
100
|
-
reader.readEndMap();
|
|
101
|
-
}
|
|
102
|
-
return accounts;
|
|
103
|
-
};
|
|
104
|
-
const readTransaction = ({ body, bodyCbor, witness }, resolved, options) => {
|
|
105
|
-
const mint = new Map([...body.mint ?? /* @__PURE__ */ new Map()].map(([assetId, quantity]) => [assetId.toLowerCase(), quantity]));
|
|
106
|
-
const { invalidBefore, invalidHereafter } = body.validityInterval ?? {};
|
|
107
|
-
return {
|
|
108
|
-
body,
|
|
109
|
-
...resolved,
|
|
110
|
-
outputs: body.outputs.map(require_outputs.viewOfOutput),
|
|
111
|
-
collateralReturn: body.collateralReturn ? require_outputs.viewOfOutput(body.collateralReturn) : void 0,
|
|
112
|
-
redeemers: witness.redeemers ?? [],
|
|
113
|
-
requiredSigners: new Set((body.requiredExtraSignatures ?? []).map((signer) => signer.toLowerCase())),
|
|
114
|
-
mintPolicies: [...new Set([...mint.keys()].map(require_outputs.policyIdOf))].sort(),
|
|
115
|
-
mint,
|
|
116
|
-
lowerBoundMs: invalidBefore === void 0 ? void 0 : options.slotToPosixMs(invalidBefore),
|
|
117
|
-
upperBoundMs: invalidHereafter === void 0 ? void 0 : options.slotToPosixMs(invalidHereafter),
|
|
118
|
-
witnessScriptHashes: new Set((witness.scripts ?? []).map((script) => _laceio_cardano_core.Serialization.Script.fromCore(script).hash())),
|
|
119
|
-
withdrawalAccounts: withdrawalAccountsOf(bodyCbor)
|
|
120
|
-
};
|
|
121
|
-
};
|
|
122
|
-
const redeemerData = (facts, purpose, index) => facts.redeemers.find((redeemer) => redeemer.purpose === purpose && redeemer.index === index)?.data;
|
|
123
|
-
const paymentCredentialOf = (view) => (view.address?.asBase() ?? view.address?.asEnterprise() ?? view.address?.asPointer())?.getPaymentCredential();
|
|
124
|
-
const stakeCredentialOf = (certificate) => "stakeCredential" in certificate ? certificate.stakeCredential : void 0;
|
|
125
|
-
const scriptHashOfRewardAccount = (rewardAccount) => {
|
|
126
|
-
const credential = _laceio_cardano_core.Cardano.Address.fromString(rewardAccount)?.asReward()?.getPaymentCredential();
|
|
127
|
-
return credential?.type === _laceio_cardano_core.Cardano.CredentialType.ScriptHash ? credential.hash : void 0;
|
|
128
|
-
};
|
|
129
|
-
//#endregion
|
|
130
|
-
//#region ../../packages/lib/cardano-custody/src/analysis/grant-spend.ts
|
|
131
|
-
const valueAt = (views, plutusAddressKey) => require_outputs.addValueDeltas(views.filter((view) => view.plutusAddressKey !== void 0 && view.plutusAddressKey === plutusAddressKey).map((view) => view.value));
|
|
132
|
-
/** `leaving_value` over outputs that are already read. */
|
|
133
|
-
const leavingValueAt = (inputs, outputs, plutusAddressKey) => require_outputs.addValueDeltas([valueAt(inputs, plutusAddressKey), require_outputs.negateValueDelta(valueAt(outputs, plutusAddressKey))]);
|
|
134
|
-
/**
|
|
135
|
-
* Every rule of `stays_within_scope` a net outflow breaks: the scoped asset
|
|
136
|
-
* within its per call and remaining caps; for a token scope, lovelace within
|
|
137
|
-
* both lovelace caps; and no other asset class leaving. A net inflow breaks
|
|
138
|
-
* nothing.
|
|
139
|
-
*
|
|
140
|
-
* @param accountFee - the fee the account pays, which `leaving.lovelace`
|
|
141
|
-
* already includes; each violation whose outflow is lovelace reports it
|
|
142
|
-
* as `fee`
|
|
143
|
-
*/
|
|
144
|
-
const findCustodyScopeViolations = (scope, leaving, accountFee) => {
|
|
145
|
-
const violations = [];
|
|
146
|
-
const isLovelaceScope = require_outputs.isCustodyLovelaceAsset(scope.asset);
|
|
147
|
-
const lovelaceFee = accountFee === void 0 ? {} : { fee: accountFee };
|
|
148
|
-
const assetFee = isLovelaceScope ? lovelaceFee : {};
|
|
149
|
-
const assetOutflow = require_outputs.quantityOfAsset(leaving, scope.asset);
|
|
150
|
-
if (assetOutflow > scope.perCallCap) violations.push({
|
|
151
|
-
code: "per-call-cap-exceeded",
|
|
152
|
-
outflow: assetOutflow,
|
|
153
|
-
limit: scope.perCallCap,
|
|
154
|
-
...assetFee
|
|
155
|
-
});
|
|
156
|
-
if (assetOutflow > scope.cap) violations.push({
|
|
157
|
-
code: "cap-exceeded",
|
|
158
|
-
outflow: assetOutflow,
|
|
159
|
-
limit: scope.cap,
|
|
160
|
-
...assetFee
|
|
161
|
-
});
|
|
162
|
-
if (!isLovelaceScope) {
|
|
163
|
-
if (leaving.lovelace > scope.lovelacePerCallCap) violations.push({
|
|
164
|
-
code: "lovelace-per-call-cap-exceeded",
|
|
165
|
-
outflow: leaving.lovelace,
|
|
166
|
-
limit: scope.lovelacePerCallCap,
|
|
167
|
-
...lovelaceFee
|
|
168
|
-
});
|
|
169
|
-
if (leaving.lovelace > scope.lovelaceCap) violations.push({
|
|
170
|
-
code: "lovelace-cap-exceeded",
|
|
171
|
-
outflow: leaving.lovelace,
|
|
172
|
-
limit: scope.lovelaceCap,
|
|
173
|
-
...lovelaceFee
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
for (const [assetId, quantity] of leaving.assets) if (quantity > 0n && !require_outputs.isAssetIdOf(assetId, scope.asset)) violations.push({
|
|
177
|
-
code: "foreign-asset-outflow",
|
|
178
|
-
assetId,
|
|
179
|
-
outflow: quantity
|
|
180
|
-
});
|
|
181
|
-
return violations;
|
|
182
|
-
};
|
|
183
|
-
const clampedOutflow = (quantity) => quantity > 0n ? quantity : 0n;
|
|
184
|
-
/**
|
|
185
|
-
* `grant_after_spend`: the grant with `cap`, and for a token grant
|
|
186
|
-
* `lovelaceCap`, lowered by the net outflow of its asset, never raised by a
|
|
187
|
-
* net deposit. These are the most the recreated grant may keep: the
|
|
188
|
-
* contract accepts any cap from zero to this value, so a builder may write
|
|
189
|
-
* lower caps that already count a fee it has not yet computed.
|
|
190
|
-
*/
|
|
191
|
-
const custodyGrantAfterSpend = (grant, leaving) => {
|
|
192
|
-
const { scope } = grant;
|
|
193
|
-
return {
|
|
194
|
-
...grant,
|
|
195
|
-
scope: {
|
|
196
|
-
...scope,
|
|
197
|
-
cap: scope.cap - clampedOutflow(require_outputs.quantityOfAsset(leaving, scope.asset)),
|
|
198
|
-
lovelaceCap: require_outputs.isCustodyLovelaceAsset(scope.asset) ? scope.lovelaceCap : scope.lovelaceCap - clampedOutflow(leaving.lovelace)
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
};
|
|
202
|
-
/** `ends_before_expiry`: a finite upper bound no later than the expiry. */
|
|
203
|
-
const findExpiryViolation = (upperBoundMs, expiresAt) => {
|
|
204
|
-
if (upperBoundMs === void 0) return { code: "no-upper-bound" };
|
|
205
|
-
return Number.isSafeInteger(upperBoundMs) && BigInt(upperBoundMs) <= expiresAt ? void 0 : {
|
|
206
|
-
code: "expired",
|
|
207
|
-
upperBoundMs,
|
|
208
|
-
expiresAt
|
|
209
|
-
};
|
|
210
|
-
};
|
|
211
|
-
const findMisplacedAccountTokens = (facts) => [["input", facts.inputs], ["output", facts.outputs]].flatMap(([kind, views]) => views.flatMap((view, index) => require_outputs.holdsMisplacedAccountToken(view, [facts.account.accountValidatorHash]) ? [{
|
|
212
|
-
code: "account-token-misplaced",
|
|
213
|
-
location: {
|
|
214
|
-
kind,
|
|
215
|
-
index
|
|
216
|
-
}
|
|
217
|
-
}] : []));
|
|
218
|
-
/** `pays_only_recipients` and `deposits_are_plain`, per output. */
|
|
219
|
-
const findOutputViolations = (facts, grant) => {
|
|
220
|
-
const { recipients } = grant.scope;
|
|
221
|
-
const recipientKeys = new Set(recipients.map(require_outputs.recipientKey));
|
|
222
|
-
const { plutusAddressKey } = facts.grantInput;
|
|
223
|
-
return facts.outputs.flatMap((view, outputIndex) => {
|
|
224
|
-
const violations = [];
|
|
225
|
-
const isBack = view.plutusAddressKey !== void 0 && view.plutusAddressKey === plutusAddressKey;
|
|
226
|
-
if (recipients.length > 0 && !isBack && (view.plutusAddressKey === void 0 || !recipientKeys.has(view.plutusAddressKey))) violations.push({
|
|
227
|
-
code: "recipient-not-allowed",
|
|
228
|
-
outputIndex
|
|
229
|
-
});
|
|
230
|
-
if (isBack && !require_outputs.holdsAccountToken(view, facts.account) && (view.datumKind !== "none" || view.hasReferenceScript)) violations.push({
|
|
231
|
-
code: "deposit-not-plain",
|
|
232
|
-
outputIndex
|
|
233
|
-
});
|
|
234
|
-
return violations;
|
|
235
|
-
});
|
|
236
|
-
};
|
|
237
|
-
/** `referenced_control`: the account's control UTxO among the reference inputs, exactly once. */
|
|
238
|
-
const findReferencedControl = (facts) => {
|
|
239
|
-
const controls = facts.referenceInputs.filter((view) => require_outputs.isControlOutput(view, facts.account));
|
|
240
|
-
if (controls.length !== 1) return { violations: [{
|
|
241
|
-
code: "referenced-control-count",
|
|
242
|
-
count: controls.length
|
|
243
|
-
}] };
|
|
244
|
-
const state = require_outputs.decodedInlineState(controls[0]);
|
|
245
|
-
return state ? {
|
|
246
|
-
state,
|
|
247
|
-
violations: []
|
|
248
|
-
} : { violations: [{ code: "referenced-control-datum" }] };
|
|
249
|
-
};
|
|
250
|
-
/** `is_current`, naming why a grant is not current against the referenced state. */
|
|
251
|
-
const findCurrencyViolation = (grant, state) => {
|
|
252
|
-
if (grant.generation < state.grantGeneration) return {
|
|
253
|
-
code: "grant-not-current",
|
|
254
|
-
liveness: "superseded"
|
|
255
|
-
};
|
|
256
|
-
if (grant.generation > state.grantGeneration) return {
|
|
257
|
-
code: "grant-not-current",
|
|
258
|
-
liveness: "future-generation"
|
|
259
|
-
};
|
|
260
|
-
return state.revoked.includes(grant.slot) ? {
|
|
261
|
-
code: "grant-not-current",
|
|
262
|
-
liveness: "revoked"
|
|
263
|
-
} : void 0;
|
|
264
|
-
};
|
|
265
|
-
const isSameExceptCaps = (actual, expected) => {
|
|
266
|
-
const a = actual.scope;
|
|
267
|
-
const b = expected.scope;
|
|
268
|
-
return actual.slot === expected.slot && actual.grantee === expected.grantee && actual.generation === expected.generation && a.asset.policyId === b.asset.policyId && a.asset.assetName === b.asset.assetName && a.perCallCap === b.perCallCap && a.lovelacePerCallCap === b.lovelacePerCallCap && a.expiresAt === b.expiresAt && a.recipients.length === b.recipients.length && a.recipients.every((recipient, index) => require_outputs.recipientKey(recipient) === require_outputs.recipientKey(b.recipients[index]));
|
|
269
|
-
};
|
|
270
|
-
const isSameValue = (a, b) => a.lovelace === b.lovelace && a.assets.size === b.assets.size && [...a.assets].every(([assetId, quantity]) => b.assets.get(assetId) === quantity);
|
|
271
|
-
/**
|
|
272
|
-
* `find_grant_output` and the checks on it: one output holding the grant
|
|
273
|
-
* token, at the spent UTxO's address with its exact value and no reference
|
|
274
|
-
* script, carrying the grant with every field kept but caps lowered within
|
|
275
|
-
* `carries_grant_within`.
|
|
276
|
-
*/
|
|
277
|
-
const findGrantOutputViolations = (facts, grant, expected) => {
|
|
278
|
-
const assetId = require_outputs.grantAssetIdOf(facts.account, grant.slot);
|
|
279
|
-
const holders = facts.outputs.flatMap((view, outputIndex) => assetId !== void 0 && view.value.assets.get(assetId) === 1n ? [{
|
|
280
|
-
view,
|
|
281
|
-
outputIndex
|
|
282
|
-
}] : []);
|
|
283
|
-
if (holders.length !== 1) return [{
|
|
284
|
-
code: "grant-output-count",
|
|
285
|
-
count: holders.length
|
|
286
|
-
}];
|
|
287
|
-
const [{ view, outputIndex }] = holders;
|
|
288
|
-
const violations = [];
|
|
289
|
-
if (view.plutusAddressKey === void 0 || view.plutusAddressKey !== facts.grantInput.plutusAddressKey) violations.push({
|
|
290
|
-
code: "grant-output-address",
|
|
291
|
-
outputIndex
|
|
292
|
-
});
|
|
293
|
-
if (!isSameValue(view.value, facts.grantInput.value)) violations.push({
|
|
294
|
-
code: "grant-output-value",
|
|
295
|
-
outputIndex
|
|
296
|
-
});
|
|
297
|
-
if (view.hasReferenceScript) violations.push({
|
|
298
|
-
code: "grant-output-reference-script",
|
|
299
|
-
outputIndex
|
|
300
|
-
});
|
|
301
|
-
if (view.inlineDatum === void 0) return [...violations, {
|
|
302
|
-
code: "grant-output-datum",
|
|
303
|
-
outputIndex,
|
|
304
|
-
reason: "not-inline"
|
|
305
|
-
}];
|
|
306
|
-
const actual = require_outputs.decodedInlineGrant(view);
|
|
307
|
-
if (!actual) return [...violations, {
|
|
308
|
-
code: "grant-output-datum",
|
|
309
|
-
outputIndex,
|
|
310
|
-
reason: "malformed"
|
|
311
|
-
}];
|
|
312
|
-
if (!isSameExceptCaps(actual, expected)) violations.push({
|
|
313
|
-
code: "grant-output-datum",
|
|
314
|
-
outputIndex,
|
|
315
|
-
reason: "field-changed"
|
|
316
|
-
});
|
|
317
|
-
for (const field of ["cap", "lovelaceCap"]) {
|
|
318
|
-
const capActual = actual.scope[field];
|
|
319
|
-
const capExpected = expected.scope[field];
|
|
320
|
-
if (capActual < 0n || capActual > capExpected) violations.push({
|
|
321
|
-
code: "grant-output-cap",
|
|
322
|
-
outputIndex,
|
|
323
|
-
field,
|
|
324
|
-
actual: capActual,
|
|
325
|
-
expected: capExpected
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
return violations;
|
|
329
|
-
};
|
|
330
|
-
/** Every violation of a grant spend whose views are already read. */
|
|
331
|
-
const grantSpendViolationsOf = (facts) => {
|
|
332
|
-
const grant = require_outputs.decodedInlineGrant(facts.grantInput);
|
|
333
|
-
if (!grant) return [{ code: "grant-datum-invalid" }];
|
|
334
|
-
const violations = [];
|
|
335
|
-
if (!require_outputs.holdsOnlyLovelaceAndGrantToken(facts.grantInput.value, facts.account, grant.slot)) violations.push({ code: "grant-input-value" });
|
|
336
|
-
const tokenInputs = facts.inputs.filter((view) => require_outputs.holdsAccountToken(view, facts.account)).length;
|
|
337
|
-
if (tokenInputs !== 1) violations.push({
|
|
338
|
-
code: "account-token-inputs",
|
|
339
|
-
count: tokenInputs
|
|
340
|
-
});
|
|
341
|
-
const control = findReferencedControl(facts);
|
|
342
|
-
violations.push(...control.violations);
|
|
343
|
-
const currency = control.state && findCurrencyViolation(grant, control.state);
|
|
344
|
-
if (currency) violations.push(currency);
|
|
345
|
-
if (!facts.requiredSigners.has(grant.grantee)) violations.push({
|
|
346
|
-
code: "grantee-not-signer",
|
|
347
|
-
grantee: grant.grantee
|
|
348
|
-
});
|
|
349
|
-
const expiry = findExpiryViolation(facts.upperBoundMs, grant.scope.expiresAt);
|
|
350
|
-
if (expiry) violations.push(expiry);
|
|
351
|
-
const leaving = leavingValueAt(facts.inputs, facts.outputs, facts.grantInput.plutusAddressKey);
|
|
352
|
-
return [
|
|
353
|
-
...violations,
|
|
354
|
-
...findCustodyScopeViolations(grant.scope, leaving),
|
|
355
|
-
...findOutputViolations(facts, grant),
|
|
356
|
-
...findMisplacedAccountTokens(facts),
|
|
357
|
-
...findGrantOutputViolations(facts, grant, custodyGrantAfterSpend(grant, leaving)),
|
|
358
|
-
...[...facts.mint].filter(([assetId]) => require_outputs.policyIdOf(assetId) === facts.account.accountValidatorHash).map(([assetId, quantity]) => ({
|
|
359
|
-
code: "mint-under-policy",
|
|
360
|
-
assetId,
|
|
361
|
-
quantity
|
|
362
|
-
}))
|
|
363
|
-
];
|
|
364
|
-
};
|
|
365
|
-
//#endregion
|
|
366
|
-
//#region ../../packages/lib/cardano-custody/src/record.ts
|
|
367
|
-
const CUSTODY_ACCOUNT_RECORD_TYPE = "cardano-custody-account";
|
|
368
|
-
const createCustodyAccountRecord = (identity) => canonicalCustodyAccountRecord({
|
|
369
|
-
type: CUSTODY_ACCOUNT_RECORD_TYPE,
|
|
370
|
-
version: 1,
|
|
371
|
-
networkMagic: identity.chainId.networkMagic,
|
|
372
|
-
accountValidatorHash: identity.contractVersion.accountValidatorHash,
|
|
373
|
-
creatorKeyHash: identity.creatorKeyHash,
|
|
374
|
-
stakeScriptHash: identity.stakeScriptHash,
|
|
375
|
-
address: identity.address
|
|
376
|
-
});
|
|
377
|
-
/** A copy holding exactly the record fields, in their canonical order. */
|
|
378
|
-
const canonicalCustodyAccountRecord = (record) => ({
|
|
379
|
-
type: record.type,
|
|
380
|
-
version: record.version,
|
|
381
|
-
networkMagic: record.networkMagic,
|
|
382
|
-
accountValidatorHash: record.accountValidatorHash,
|
|
383
|
-
creatorKeyHash: record.creatorKeyHash,
|
|
384
|
-
stakeScriptHash: record.stakeScriptHash,
|
|
385
|
-
address: record.address
|
|
386
|
-
});
|
|
387
|
-
const RECORD_FIELDS = Object.keys(canonicalCustodyAccountRecord({}));
|
|
388
|
-
const UPSTREAM_RECORD_FIELDS = [
|
|
389
|
-
"owner",
|
|
390
|
-
"stakeScriptHash",
|
|
391
|
-
"address"
|
|
392
|
-
];
|
|
393
|
-
const malformed$1 = (reason, field) => ({
|
|
394
|
-
step: 1,
|
|
395
|
-
code: "malformed-record",
|
|
396
|
-
reason,
|
|
397
|
-
...field === void 0 ? {} : { field }
|
|
398
|
-
});
|
|
399
|
-
const parseJson = (input) => {
|
|
400
|
-
if (typeof input !== "string") return input;
|
|
401
|
-
try {
|
|
402
|
-
return JSON.parse(input);
|
|
403
|
-
} catch {
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
};
|
|
407
|
-
const isPlainObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
408
|
-
const hasExactFields$1 = (value, fields) => {
|
|
409
|
-
const keys = Object.keys(value);
|
|
410
|
-
return keys.length === fields.length && fields.every((field) => keys.includes(field));
|
|
411
|
-
};
|
|
412
|
-
const parseAddress = (value) => typeof value === "string" && _laceio_cardano_core.Cardano.Address.isValidBech32(value) ? _laceio_cardano_core.Cardano.Address.fromBech32(value) : null;
|
|
413
|
-
const HASH_FIELDS = [
|
|
414
|
-
"accountValidatorHash",
|
|
415
|
-
"creatorKeyHash",
|
|
416
|
-
"stakeScriptHash"
|
|
417
|
-
];
|
|
418
|
-
const readRecordClaims = (value) => {
|
|
419
|
-
if (!hasExactFields$1(value, RECORD_FIELDS)) return require_hex.fail(malformed$1(`expected exactly ${RECORD_FIELDS.join(", ")}`));
|
|
420
|
-
if (value.type !== "cardano-custody-account") return require_hex.fail(malformed$1("unknown record type", "type"));
|
|
421
|
-
if (value.version !== 1) return require_hex.fail(malformed$1("unsupported record version", "version"));
|
|
422
|
-
if (!require_identity.isNetworkMagic(value.networkMagic)) return require_hex.fail(malformed$1("expected a network magic", "networkMagic"));
|
|
423
|
-
const invalidHash = HASH_FIELDS.find((field) => !require_hex.isHash28Hex(value[field]));
|
|
424
|
-
if (invalidHash) return require_hex.fail(malformed$1("expected 28 bytes of lower-case hex", invalidHash));
|
|
425
|
-
const address = parseAddress(value.address);
|
|
426
|
-
if (!address) return require_hex.fail(malformed$1("expected an address", "address"));
|
|
427
|
-
return require_hex.ok({
|
|
428
|
-
networkMagic: value.networkMagic,
|
|
429
|
-
accountValidatorHash: value.accountValidatorHash,
|
|
430
|
-
creatorKeyHash: value.creatorKeyHash,
|
|
431
|
-
stakeScriptHash: value.stakeScriptHash,
|
|
432
|
-
address,
|
|
433
|
-
addressText: value.address
|
|
434
|
-
});
|
|
435
|
-
};
|
|
436
|
-
/**
|
|
437
|
-
* Claims of the upstream `{ owner, stakeScriptHash, address }` record. It
|
|
438
|
-
* names no network or contract version: the network is the selected one, and
|
|
439
|
-
* the account validator is the address's payment script.
|
|
440
|
-
*/
|
|
441
|
-
const readUpstreamRecordClaims = (value, selectedNetworkMagic) => {
|
|
442
|
-
if (!hasExactFields$1(value, UPSTREAM_RECORD_FIELDS)) return require_hex.fail(malformed$1(`expected exactly ${UPSTREAM_RECORD_FIELDS.join(", ")}`));
|
|
443
|
-
const invalidHash = ["owner", "stakeScriptHash"].find((field) => !require_hex.isHash28Hex(value[field]));
|
|
444
|
-
if (invalidHash) return require_hex.fail(malformed$1("expected 28 bytes of lower-case hex", invalidHash));
|
|
445
|
-
const address = parseAddress(value.address);
|
|
446
|
-
const payment = address?.asBase()?.getPaymentCredential();
|
|
447
|
-
if (!address || payment?.type !== _laceio_cardano_core.Cardano.CredentialType.ScriptHash) return require_hex.fail(malformed$1("expected a script base address", "address"));
|
|
448
|
-
return require_hex.ok({
|
|
449
|
-
networkMagic: selectedNetworkMagic,
|
|
450
|
-
accountValidatorHash: payment.hash,
|
|
451
|
-
creatorKeyHash: value.owner,
|
|
452
|
-
stakeScriptHash: value.stakeScriptHash,
|
|
453
|
-
address,
|
|
454
|
-
addressText: value.address
|
|
455
|
-
});
|
|
456
|
-
};
|
|
457
|
-
const readClaims = (input, selectedNetworkMagic) => {
|
|
458
|
-
const value = parseJson(input);
|
|
459
|
-
if (!isPlainObject$1(value)) return require_hex.fail(malformed$1("expected a JSON object"));
|
|
460
|
-
return "owner" in value ? readUpstreamRecordClaims(value, selectedNetworkMagic) : readRecordClaims(value);
|
|
461
|
-
};
|
|
462
|
-
/**
|
|
463
|
-
* Validates an imported record, in order: (1) known type, version and field
|
|
464
|
-
* shapes; (2) a supported contract version; (3) the stake script hash
|
|
465
|
-
* re-derived from the creator key hash; (4) the address re-derived for the
|
|
466
|
-
* record's network, byte for byte; (5) the record's network is the selected
|
|
467
|
-
* one. Step 3 is what proves the record names a genuine custody account: the
|
|
468
|
-
* mint accepts any stake script.
|
|
469
|
-
*
|
|
470
|
-
* Also accepts the upstream `{ owner, stakeScriptHash, address }` record,
|
|
471
|
-
* taking the network from `selectedNetworkMagic`.
|
|
472
|
-
*
|
|
473
|
-
* @param input - the record as JSON text or as an already parsed value
|
|
474
|
-
*/
|
|
475
|
-
const parseCustodyAccountRecord = (input, selectedNetworkMagic) => {
|
|
476
|
-
const claims = readClaims(input, selectedNetworkMagic);
|
|
477
|
-
if (!claims.ok) return claims;
|
|
478
|
-
const { networkMagic, accountValidatorHash, creatorKeyHash, stakeScriptHash, address, addressText } = claims.value;
|
|
479
|
-
const contractVersion = require_hex.findCustodyContractVersion(accountValidatorHash);
|
|
480
|
-
if (!contractVersion.ok) return require_hex.fail({
|
|
481
|
-
step: 2,
|
|
482
|
-
...contractVersion.error
|
|
483
|
-
});
|
|
484
|
-
const identity = require_identity.identityOfValidatedCreator({
|
|
485
|
-
contractVersion: contractVersion.value,
|
|
486
|
-
creatorKeyHash,
|
|
487
|
-
chainId: require_identity.chainIdFromNetworkMagic(networkMagic)
|
|
488
|
-
});
|
|
489
|
-
if (identity.stakeScriptHash !== stakeScriptHash) return require_hex.fail({
|
|
490
|
-
step: 3,
|
|
491
|
-
code: "stake-script-hash-mismatch",
|
|
492
|
-
expected: identity.stakeScriptHash,
|
|
493
|
-
actual: stakeScriptHash
|
|
494
|
-
});
|
|
495
|
-
const derivedAddress = identity.address;
|
|
496
|
-
if (_laceio_cardano_core.Cardano.Address.fromBech32(derivedAddress).toBytes() !== address.toBytes()) return require_hex.fail({
|
|
497
|
-
step: 4,
|
|
498
|
-
code: "address-mismatch",
|
|
499
|
-
expected: derivedAddress,
|
|
500
|
-
actual: addressText
|
|
501
|
-
});
|
|
502
|
-
if (networkMagic !== selectedNetworkMagic) return require_hex.fail({
|
|
503
|
-
step: 5,
|
|
504
|
-
code: "network-mismatch",
|
|
505
|
-
expected: selectedNetworkMagic,
|
|
506
|
-
actual: networkMagic
|
|
507
|
-
});
|
|
508
|
-
return require_hex.ok({
|
|
509
|
-
record: createCustodyAccountRecord(identity),
|
|
510
|
-
identity
|
|
511
|
-
});
|
|
512
|
-
};
|
|
513
|
-
//#endregion
|
|
514
|
-
//#region ../../packages/lib/cardano-custody/src/codecs/redeemers.ts
|
|
515
|
-
/** Constructor order of `AccountRedeemer`; no constructor has fields. */
|
|
516
|
-
const ACCOUNT_REDEEMER_KINDS = [
|
|
517
|
-
"device",
|
|
518
|
-
"spendWithGrant",
|
|
519
|
-
"sweepGrant",
|
|
520
|
-
"fund"
|
|
521
|
-
];
|
|
522
|
-
/** Constructor order of `MintRedeemer`; no constructor has fields. */
|
|
523
|
-
const MINT_REDEEMER_KINDS = [
|
|
524
|
-
"createAccount",
|
|
525
|
-
"issueGrants",
|
|
526
|
-
"burnGrants"
|
|
527
|
-
];
|
|
528
|
-
/** Decodes a union whose constructors all have no fields. */
|
|
529
|
-
const decodeFieldless = (data, kinds, what) => require_outputs.decodeResult(() => {
|
|
530
|
-
const index = require_outputs.expectConstrIndex(data, "", what);
|
|
531
|
-
const kind = kinds[Number(index)];
|
|
532
|
-
if (kind === void 0) return require_outputs.unknownAlternative("", `${what} of ${kinds.join(", ")}`, index);
|
|
533
|
-
require_outputs.expectConstr(data, {
|
|
534
|
-
index: Number(index),
|
|
535
|
-
arity: 0
|
|
536
|
-
}, {
|
|
537
|
-
path: "",
|
|
538
|
-
what
|
|
539
|
-
});
|
|
540
|
-
return { kind };
|
|
541
|
-
});
|
|
542
|
-
/** `Device` is constructor 0, `SpendWithGrant` 1, `SweepGrant` 2 and `Fund` 3. */
|
|
543
|
-
const encodeCustodyAccountRedeemer = ({ kind }) => require_outputs.constr(ACCOUNT_REDEEMER_KINDS.indexOf(kind));
|
|
544
|
-
const decodeCustodyAccountRedeemer = (data) => decodeFieldless(data, ACCOUNT_REDEEMER_KINDS, "an account redeemer");
|
|
545
|
-
/** `CreateAccount` is constructor 0, `IssueGrants` 1 and `BurnGrants` 2. */
|
|
546
|
-
const encodeCustodyMintRedeemer = ({ kind }) => require_outputs.constr(MINT_REDEEMER_KINDS.indexOf(kind));
|
|
547
|
-
const decodeCustodyMintRedeemer = (data) => decodeFieldless(data, MINT_REDEEMER_KINDS, "a mint redeemer");
|
|
548
|
-
/** `Operate`: constructor 0 with no fields. */
|
|
549
|
-
const encodeCustodyStakeRedeemer = () => require_outputs.constr(0);
|
|
550
|
-
/**
|
|
551
|
-
* `Run`, the redeemer of a logic script's withdraw and publish handlers:
|
|
552
|
-
* constructor 0 with no fields.
|
|
553
|
-
*/
|
|
554
|
-
const encodeCustodyLogicRedeemer = () => require_outputs.constr(0);
|
|
555
|
-
//#endregion
|
|
556
|
-
//#region ../../packages/lib/cardano-custody/src/codecs/reserve.ts
|
|
557
|
-
/** The datum the upstream library writes on a reserve: constructor 0 with no fields. */
|
|
558
|
-
const RESERVE_DATUM_CBOR = "d87980";
|
|
559
|
-
/**
|
|
560
|
-
* Whether an inline datum is, byte for byte, the reserve datum `d87980`.
|
|
561
|
-
* Any datum makes a deposit an owner-only reserve; only this one has a known
|
|
562
|
-
* size, so a builder can spend such a reserve without a surprise in fee or
|
|
563
|
-
* minimum lovelace.
|
|
564
|
-
*/
|
|
565
|
-
const isCustodyReserveDatum = (datum) => _laceio_cardano_core.Serialization.PlutusData.fromCore(datum).toCbor() === RESERVE_DATUM_CBOR;
|
|
566
|
-
//#endregion
|
|
567
|
-
//#region ../../packages/lib/cardano-custody/src/analysis/fee-shares.ts
|
|
568
|
-
const lovelaceOf = (items) => items.reduce((total, { value }) => total + value.coins, 0n);
|
|
569
|
-
const positivePart = (quantity) => quantity > 0n ? quantity : 0n;
|
|
570
|
-
/** How the fee splits between the external inputs and the operated accounts. */
|
|
571
|
-
const feeSharesOf = ({ externalInputs, externalOutputs, fee }) => {
|
|
572
|
-
const payers = new Set(externalInputs.map(({ address }) => address));
|
|
573
|
-
const beyondChange = lovelaceOf(externalInputs) - lovelaceOf(externalOutputs.filter(({ address }) => payers.has(address)));
|
|
574
|
-
const externalShare = beyondChange < fee ? positivePart(beyondChange) : fee;
|
|
575
|
-
return {
|
|
576
|
-
payers: [...payers],
|
|
577
|
-
externalShare,
|
|
578
|
-
accountShare: fee - externalShare
|
|
579
|
-
};
|
|
580
|
-
};
|
|
581
|
-
/**
|
|
582
|
-
* The part of the accounts' fee share each operated account pays, from the
|
|
583
|
-
* lovelace each gives up. Each pays only what the others cannot cover, so an
|
|
584
|
-
* account is named as paying someone whenever some split of the fee has it
|
|
585
|
-
* do so. A single account pays the whole share.
|
|
586
|
-
*/
|
|
587
|
-
const accountFeeSharesOf = (givenUp, accountShare) => {
|
|
588
|
-
const total = givenUp.reduce((sum, lovelace) => sum + positivePart(lovelace), 0n);
|
|
589
|
-
return givenUp.map((lovelace) => positivePart(accountShare - (total - positivePart(lovelace))));
|
|
590
|
-
};
|
|
591
|
-
//#endregion
|
|
592
|
-
//#region ../../packages/lib/cardano-custody/src/analysis/state-change.ts
|
|
593
|
-
const isSameOrder = (before, after) => {
|
|
594
|
-
const kept = before.filter((item) => after.includes(item));
|
|
595
|
-
const keptAfter = after.filter((item) => before.includes(item));
|
|
596
|
-
return kept.every((item, index) => item === keptAfter[index]);
|
|
597
|
-
};
|
|
598
|
-
const counterChange = (before, after) => before === after ? void 0 : {
|
|
599
|
-
before,
|
|
600
|
-
after
|
|
601
|
-
};
|
|
602
|
-
const diffCustodyStates = (before, after) => {
|
|
603
|
-
const generation = counterChange(before.grantGeneration, after.grantGeneration);
|
|
604
|
-
const nextSlot = counterChange(before.nextSlot, after.nextSlot);
|
|
605
|
-
const outstanding = counterChange(before.outstanding, after.outstanding);
|
|
606
|
-
return {
|
|
607
|
-
before,
|
|
608
|
-
after,
|
|
609
|
-
...before.logic === after.logic ? {} : { logic: {
|
|
610
|
-
before: before.logic,
|
|
611
|
-
after: after.logic
|
|
612
|
-
} },
|
|
613
|
-
devicesAdded: after.devices.filter((device) => !before.devices.includes(device)),
|
|
614
|
-
devicesRemoved: before.devices.filter((device) => !after.devices.includes(device)),
|
|
615
|
-
revokedAdded: after.revoked.filter((slot) => !before.revoked.includes(slot)),
|
|
616
|
-
revokedRemoved: before.revoked.filter((slot) => !after.revoked.includes(slot)),
|
|
617
|
-
...generation ? { grantGeneration: generation } : {},
|
|
618
|
-
...nextSlot ? { nextSlot } : {},
|
|
619
|
-
...outstanding ? { outstanding } : {},
|
|
620
|
-
isReordered: !isSameOrder(before.devices, after.devices) || !isSameOrder(before.revoked, after.revoked)
|
|
621
|
-
};
|
|
622
|
-
};
|
|
623
|
-
//#endregion
|
|
624
|
-
//#region ../../packages/lib/cardano-custody/src/analysis/account-inspection.ts
|
|
625
|
-
const accountKey = (account) => `${account.accountValidatorHash}${account.stakeScriptHash}`;
|
|
626
|
-
const hashesOf = (account) => ({
|
|
627
|
-
accountValidatorHash: account.accountValidatorHash,
|
|
628
|
-
stakeScriptHash: account.stakeScriptHash
|
|
629
|
-
});
|
|
630
|
-
const versionOfPolicy = (policyId) => require_hex.SUPPORTED_CUSTODY_CONTRACT_VERSIONS.find((version) => version.accountValidatorHash === policyId);
|
|
631
|
-
/**
|
|
632
|
-
* Every custody account the transaction names: by an input, reference input
|
|
633
|
-
* or output at its address, by a mint of one of its account tokens, or by
|
|
634
|
-
* the record.
|
|
635
|
-
*/
|
|
636
|
-
const findCandidates = (facts, record) => {
|
|
637
|
-
const candidates = /* @__PURE__ */ new Map();
|
|
638
|
-
const add = (candidate) => candidates.set(accountKey(candidate), candidate);
|
|
639
|
-
const views = [
|
|
640
|
-
...facts.inputs.map(({ output }) => output),
|
|
641
|
-
...facts.referenceInputs.map(({ output }) => output),
|
|
642
|
-
...facts.outputs
|
|
643
|
-
];
|
|
644
|
-
for (const { addressClass } of views) if (addressClass.kind === "account") add({
|
|
645
|
-
contractVersion: addressClass.contractVersion,
|
|
646
|
-
accountValidatorHash: addressClass.contractVersion.accountValidatorHash,
|
|
647
|
-
stakeScriptHash: addressClass.stakeScriptHash
|
|
648
|
-
});
|
|
649
|
-
for (const assetId of facts.mint.keys()) {
|
|
650
|
-
const contractVersion = versionOfPolicy(require_outputs.policyIdOf(assetId));
|
|
651
|
-
const name = require_tokens.parseCustodyAccountTokenName(require_outputs.assetNameOf(assetId));
|
|
652
|
-
if (contractVersion && name) add({
|
|
653
|
-
contractVersion,
|
|
654
|
-
accountValidatorHash: contractVersion.accountValidatorHash,
|
|
655
|
-
stakeScriptHash: name.stakeScriptHash
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
if (record) add({
|
|
659
|
-
contractVersion: record.contractVersion,
|
|
660
|
-
accountValidatorHash: record.contractVersion.accountValidatorHash,
|
|
661
|
-
stakeScriptHash: record.stakeScriptHash
|
|
662
|
-
});
|
|
663
|
-
return [...candidates.values()];
|
|
664
|
-
};
|
|
665
|
-
const VOTE_DELEGATIONS = new Set([
|
|
666
|
-
_laceio_cardano_core.Cardano.CertificateType.StakeRegistrationDelegation,
|
|
667
|
-
_laceio_cardano_core.Cardano.CertificateType.StakeVoteDelegation,
|
|
668
|
-
_laceio_cardano_core.Cardano.CertificateType.StakeVoteRegistrationDelegation,
|
|
669
|
-
_laceio_cardano_core.Cardano.CertificateType.VoteDelegation,
|
|
670
|
-
_laceio_cardano_core.Cardano.CertificateType.VoteRegistrationDelegation
|
|
671
|
-
]);
|
|
672
|
-
/**
|
|
673
|
-
* The certificates on the account's stake credential and the withdrawal
|
|
674
|
-
* from its reward account, apart from the logic run, which withdraws from
|
|
675
|
-
* the logic's credential. Registration with a deposit and pool delegation
|
|
676
|
-
* are what Lace builds; vote and combined delegations are valid on chain
|
|
677
|
-
* but unsupported, and every other certificate is refused.
|
|
678
|
-
*/
|
|
679
|
-
const readStakeEffects = (account, facts) => {
|
|
680
|
-
const refusals = [];
|
|
681
|
-
const certificates = [];
|
|
682
|
-
let isStaked = false;
|
|
683
|
-
for (const [certificateIndex, certificate] of (facts.body.certificates ?? []).entries()) {
|
|
684
|
-
const credential = stakeCredentialOf(certificate);
|
|
685
|
-
if (credential?.type !== _laceio_cardano_core.Cardano.CredentialType.ScriptHash || credential.hash !== account.stakeScriptHash) continue;
|
|
686
|
-
isStaked = true;
|
|
687
|
-
if (certificate.__typename === _laceio_cardano_core.Cardano.CertificateType.Registration) certificates.push({
|
|
688
|
-
certificateIndex,
|
|
689
|
-
kind: "registration",
|
|
690
|
-
deposit: certificate.deposit
|
|
691
|
-
});
|
|
692
|
-
else if (certificate.__typename === _laceio_cardano_core.Cardano.CertificateType.StakeDelegation) certificates.push({
|
|
693
|
-
certificateIndex,
|
|
694
|
-
kind: "pool-delegation",
|
|
695
|
-
poolId: certificate.poolId
|
|
696
|
-
});
|
|
697
|
-
else refusals.push({
|
|
698
|
-
code: "custody-unsupported-operation",
|
|
699
|
-
reason: VOTE_DELEGATIONS.has(certificate.__typename) ? "unsupported-certificate" : "refused-certificate",
|
|
700
|
-
certificateIndex,
|
|
701
|
-
certificateType: certificate.__typename
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
const withdrawal = (facts.body.withdrawals ?? []).find(({ stakeAddress }) => scriptHashOfRewardAccount(stakeAddress) === account.stakeScriptHash);
|
|
705
|
-
return {
|
|
706
|
-
certificates,
|
|
707
|
-
withdrawal: withdrawal?.quantity,
|
|
708
|
-
refusals,
|
|
709
|
-
isStaked: isStaked || withdrawal !== void 0
|
|
710
|
-
};
|
|
711
|
-
};
|
|
712
|
-
/**
|
|
713
|
-
* The run of the logic the account operates under: the logic its spent
|
|
714
|
-
* control UTxO names, the referenced one for a grant spend, or the control
|
|
715
|
-
* output's at creation. The proxy requires a withdrawal from that logic's
|
|
716
|
-
* stake credential, which makes the ledger run it. The decoded body keeps
|
|
717
|
-
* one withdrawal per reward account; a repeat in the bytes is refused.
|
|
718
|
-
*/
|
|
719
|
-
const readLogicRun = (account, facts, logic) => {
|
|
720
|
-
if (logic === void 0) return { refusals: [] };
|
|
721
|
-
const withdrawal = (facts.body.withdrawals ?? []).find(({ stakeAddress }) => scriptHashOfRewardAccount(stakeAddress) === logic);
|
|
722
|
-
return withdrawal ? {
|
|
723
|
-
run: {
|
|
724
|
-
logic,
|
|
725
|
-
draw: withdrawal.quantity
|
|
726
|
-
},
|
|
727
|
-
refusals: []
|
|
728
|
-
} : { refusals: [{
|
|
729
|
-
code: "custody-unsupported-operation",
|
|
730
|
-
reason: "logic-withdrawal-missing",
|
|
731
|
-
account,
|
|
732
|
-
logic
|
|
733
|
-
}] };
|
|
734
|
-
};
|
|
735
|
-
/**
|
|
736
|
-
* A device whose key hash is not 28 bytes can never sign, and the contract
|
|
737
|
-
* does not check key lengths, so an on-chain state may keep one. Lace never
|
|
738
|
-
* adds one, but a device can still remove it in a rewrite that keeps it
|
|
739
|
-
* meanwhile.
|
|
740
|
-
*/
|
|
741
|
-
const isCarriedOver = (violation, before) => before !== void 0 && violation.code === "invalid-device-key-hash" && before.devices.includes(violation.device);
|
|
742
|
-
/** The authoring checks a state written by a device or a creation must pass. */
|
|
743
|
-
const checkWrittenState = ({ account, before, after }) => require_outputs.findCustodyStateViolations(after).filter((violation) => !isCarriedOver(violation, before)).map((violation) => ({
|
|
744
|
-
code: "custody-malformed-datum",
|
|
745
|
-
reason: "state-violation",
|
|
746
|
-
account,
|
|
747
|
-
violation
|
|
748
|
-
}));
|
|
749
|
-
const isSameAccount = (account, record) => record !== void 0 && record.contractVersion.accountValidatorHash === account.accountValidatorHash && record.stakeScriptHash === account.stakeScriptHash;
|
|
750
|
-
/**
|
|
751
|
-
* Lace creates only the creator-only state: the current logic of the
|
|
752
|
-
* account's contract version, the creator as the single device, generation
|
|
753
|
-
* zero and no grant issued, revoked or outstanding. The logic also accepts
|
|
754
|
-
* any generation at creation.
|
|
755
|
-
*/
|
|
756
|
-
const isCreatorOnly = (state, creator, logic) => state.logic === logic && state.devices.length === 1 && state.devices[0] === creator && state.grantGeneration === 0n && state.nextSlot === 0n && state.revoked.length === 0 && state.outstanding === 0n;
|
|
757
|
-
/**
|
|
758
|
-
* At creation the stake validator needs the creator's signature, and Lace
|
|
759
|
-
* creates only the creator-only state; otherwise a device listed in the
|
|
760
|
-
* spent state must sign.
|
|
761
|
-
*/
|
|
762
|
-
const checkSigner = ({ account, before, after }, facts, scope) => {
|
|
763
|
-
const { deviceKeyHash } = scope;
|
|
764
|
-
const refusals = [];
|
|
765
|
-
const notSigning = (keyHash) => facts.requiredSigners.has(keyHash) ? [] : [{
|
|
766
|
-
code: "custody-device-not-listed",
|
|
767
|
-
reason: "not-required-signer",
|
|
768
|
-
account,
|
|
769
|
-
keyHash
|
|
770
|
-
}];
|
|
771
|
-
if (!before) {
|
|
772
|
-
if (!isSameAccount(account, scope.record)) return scope.record ? [] : [{
|
|
773
|
-
code: "custody-context-mismatch",
|
|
774
|
-
reason: "creation-without-record",
|
|
775
|
-
account
|
|
776
|
-
}];
|
|
777
|
-
const creator = scope.record.creatorKeyHash;
|
|
778
|
-
if (deviceKeyHash !== void 0 && deviceKeyHash !== creator) refusals.push({
|
|
779
|
-
code: "custody-device-not-listed",
|
|
780
|
-
reason: "not-creator",
|
|
781
|
-
account,
|
|
782
|
-
keyHash: deviceKeyHash
|
|
783
|
-
});
|
|
784
|
-
refusals.push(...notSigning(creator));
|
|
785
|
-
if (!isCreatorOnly(after, creator, scope.record.contractVersion.currentLogicHash)) refusals.push({
|
|
786
|
-
code: "custody-context-mismatch",
|
|
787
|
-
reason: "initial-state",
|
|
788
|
-
account
|
|
789
|
-
});
|
|
790
|
-
return refusals;
|
|
791
|
-
}
|
|
792
|
-
if (deviceKeyHash === void 0) return before.devices.some((device) => facts.requiredSigners.has(device)) ? [] : [{
|
|
793
|
-
code: "custody-device-not-listed",
|
|
794
|
-
reason: "no-device-signer",
|
|
795
|
-
account
|
|
796
|
-
}];
|
|
797
|
-
if (!before.devices.includes(deviceKeyHash)) refusals.push({
|
|
798
|
-
code: "custody-device-not-listed",
|
|
799
|
-
reason: "not-listed",
|
|
800
|
-
account,
|
|
801
|
-
keyHash: deviceKeyHash
|
|
802
|
-
});
|
|
803
|
-
return [...refusals, ...notSigning(deviceKeyHash)];
|
|
804
|
-
};
|
|
805
|
-
/** The redeemers the contract lets spend each kind of account UTxO. */
|
|
806
|
-
const REDEEMERS_OF = {
|
|
807
|
-
control: ["device"],
|
|
808
|
-
grant: ["spendWithGrant", "sweepGrant"],
|
|
809
|
-
reserve: ["fund"],
|
|
810
|
-
fund: ["fund"]
|
|
811
|
-
};
|
|
812
|
-
/**
|
|
813
|
-
* The account inputs by kind, each with its spend redeemer, which must fit
|
|
814
|
-
* the UTxO it spends. A plain deposit rides on any account token spent
|
|
815
|
-
* beside it; a reserve needs the control UTxO, so only a device spends it.
|
|
816
|
-
*/
|
|
817
|
-
const readAccountInputs = (account, facts) => {
|
|
818
|
-
const refusals = [];
|
|
819
|
-
const spent = facts.inputs.flatMap((view, redeemerIndex) => {
|
|
820
|
-
if (!require_outputs.isAtAccount(view.output, account)) return [];
|
|
821
|
-
const kind = require_outputs.accountUtxoKindOf(view.output, account);
|
|
822
|
-
const data = redeemerData(facts, _laceio_cardano_core.Cardano.RedeemerPurpose.spend, redeemerIndex);
|
|
823
|
-
const decoded = data === void 0 ? void 0 : decodeCustodyAccountRedeemer(data);
|
|
824
|
-
if (!decoded?.ok) {
|
|
825
|
-
refusals.push({
|
|
826
|
-
code: "custody-unsupported-operation",
|
|
827
|
-
reason: decoded ? "unknown-redeemer" : "missing-redeemer",
|
|
828
|
-
purpose: "spend",
|
|
829
|
-
index: redeemerIndex
|
|
830
|
-
});
|
|
831
|
-
return [{
|
|
832
|
-
...view,
|
|
833
|
-
kind,
|
|
834
|
-
redeemer: void 0
|
|
835
|
-
}];
|
|
836
|
-
}
|
|
837
|
-
if (!REDEEMERS_OF[kind].includes(decoded.value.kind)) refusals.push({
|
|
838
|
-
code: "custody-context-mismatch",
|
|
839
|
-
reason: "redeemer-mismatch",
|
|
840
|
-
input: view.input,
|
|
841
|
-
redeemer: decoded.value.kind
|
|
842
|
-
});
|
|
843
|
-
return [{
|
|
844
|
-
...view,
|
|
845
|
-
kind,
|
|
846
|
-
redeemer: decoded.value
|
|
847
|
-
}];
|
|
848
|
-
});
|
|
849
|
-
const ofKind = (kind) => spent.filter((input) => input.kind === kind);
|
|
850
|
-
const controls = ofKind("control");
|
|
851
|
-
const grants = ofKind("grant");
|
|
852
|
-
const reserves = ofKind("reserve");
|
|
853
|
-
const funds = ofKind("fund");
|
|
854
|
-
if (controls.length === 0) refusals.push(...reserves.map(({ input }) => ({
|
|
855
|
-
code: "custody-unsupported-operation",
|
|
856
|
-
reason: "reserve-without-control",
|
|
857
|
-
input
|
|
858
|
-
})));
|
|
859
|
-
if (funds.length > 0 && controls.length + grants.length === 0) refusals.push({
|
|
860
|
-
code: "custody-unsupported-operation",
|
|
861
|
-
reason: "fund-without-account-token",
|
|
862
|
-
account
|
|
863
|
-
});
|
|
864
|
-
return {
|
|
865
|
-
controls,
|
|
866
|
-
spends: grants.filter(({ redeemer }) => redeemer?.kind === "spendWithGrant"),
|
|
867
|
-
sweeps: grants.filter(({ redeemer }) => redeemer?.kind === "sweepGrant"),
|
|
868
|
-
reserves,
|
|
869
|
-
funds,
|
|
870
|
-
isSpent: spent.length > 0,
|
|
871
|
-
refusals
|
|
872
|
-
};
|
|
873
|
-
};
|
|
874
|
-
const bySlot$1 = (a, b) => a.slot < b.slot ? -1 : a.slot > b.slot ? 1 : 0;
|
|
875
|
-
const mintShapeOf = (entries) => {
|
|
876
|
-
const names = entries.map(([assetId, quantity]) => ({
|
|
877
|
-
name: require_tokens.parseCustodyAccountTokenName(require_outputs.assetNameOf(assetId)),
|
|
878
|
-
quantity
|
|
879
|
-
}));
|
|
880
|
-
const [first] = names;
|
|
881
|
-
if (names.length === 1 && first.name?.kind === "state-nft") return {
|
|
882
|
-
kind: "creation",
|
|
883
|
-
stakeScriptHash: first.name.stakeScriptHash
|
|
884
|
-
};
|
|
885
|
-
const stakeScriptHash = first?.name?.stakeScriptHash;
|
|
886
|
-
if (!(stakeScriptHash !== void 0 && names.every(({ name, quantity }) => name?.kind === "grant" && name.stakeScriptHash === stakeScriptHash && (quantity === 1n || quantity === -1n)))) return { kind: "invalid" };
|
|
887
|
-
const issues = names.filter(({ quantity }) => quantity === 1n).length;
|
|
888
|
-
return {
|
|
889
|
-
kind: issues === names.length ? "issue" : issues === 0 ? "burn" : "mixed",
|
|
890
|
-
stakeScriptHash
|
|
891
|
-
};
|
|
892
|
-
};
|
|
893
|
-
/**
|
|
894
|
-
* The account's mint: its state NFT at creation, grant tokens issued at +1
|
|
895
|
-
* or burned at -1, under the one mint redeemer of the policy. A device
|
|
896
|
-
* spend of the control UTxO counts only the account's own grant tokens, so
|
|
897
|
-
* any other name under the policy beside it is refused. A mint of no valid
|
|
898
|
-
* shape is the transaction checks' to refuse.
|
|
899
|
-
*/
|
|
900
|
-
const readMint = (account, facts, hasControlInput) => {
|
|
901
|
-
const refusals = [];
|
|
902
|
-
const policy = account.accountValidatorHash;
|
|
903
|
-
const nftAssetId = require_outputs.stateNftAssetIdOf(account);
|
|
904
|
-
const entries = [...facts.mint].filter(([assetId]) => require_outputs.policyIdOf(assetId) === policy);
|
|
905
|
-
const shape = mintShapeOf(entries);
|
|
906
|
-
const isOwnGrantMint = shape.kind !== "invalid" && shape.kind !== "creation" && shape.stakeScriptHash === account.stakeScriptHash;
|
|
907
|
-
const issued = [];
|
|
908
|
-
const burned = [];
|
|
909
|
-
for (const [assetId, quantity] of entries) {
|
|
910
|
-
const name = require_tokens.parseCustodyAccountTokenName(require_outputs.assetNameOf(assetId));
|
|
911
|
-
if (isOwnGrantMint && name?.kind === "grant") (quantity === 1n ? issued : burned).push({
|
|
912
|
-
slot: name.slot,
|
|
913
|
-
assetId
|
|
914
|
-
});
|
|
915
|
-
else if (shape.kind !== "invalid" && shape.kind !== "mixed" && (hasControlInput || assetId === nftAssetId && quantity !== 1n)) refusals.push({
|
|
916
|
-
code: "custody-unsupported-operation",
|
|
917
|
-
reason: "unsupported-mint",
|
|
918
|
-
assetId,
|
|
919
|
-
quantity
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
issued.sort(bySlot$1);
|
|
923
|
-
burned.sort(bySlot$1);
|
|
924
|
-
const isCreation = facts.mint.has(nftAssetId);
|
|
925
|
-
if (isCreation || isOwnGrantMint) {
|
|
926
|
-
const index = facts.mintPolicies.indexOf(policy);
|
|
927
|
-
const data = redeemerData(facts, _laceio_cardano_core.Cardano.RedeemerPurpose.mint, index);
|
|
928
|
-
const decoded = data === void 0 ? void 0 : decodeCustodyMintRedeemer(data);
|
|
929
|
-
const expected = {
|
|
930
|
-
creation: "createAccount",
|
|
931
|
-
issue: "issueGrants",
|
|
932
|
-
burn: "burnGrants"
|
|
933
|
-
};
|
|
934
|
-
if (!decoded?.ok) refusals.push({
|
|
935
|
-
code: "custody-unsupported-operation",
|
|
936
|
-
reason: decoded ? "unknown-redeemer" : "missing-redeemer",
|
|
937
|
-
purpose: "mint",
|
|
938
|
-
index
|
|
939
|
-
});
|
|
940
|
-
else if (expected[shape.kind] !== void 0 && decoded.value.kind !== expected[shape.kind]) refusals.push({
|
|
941
|
-
code: "custody-unsupported-operation",
|
|
942
|
-
reason: "mint-redeemer-mismatch",
|
|
943
|
-
account,
|
|
944
|
-
redeemer: decoded.value.kind
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
if (isOwnGrantMint && !hasControlInput) refusals.push({
|
|
948
|
-
code: "custody-unsupported-operation",
|
|
949
|
-
reason: "grant-mint-without-control",
|
|
950
|
-
account
|
|
951
|
-
});
|
|
952
|
-
for (const count of [issued.length, burned.length]) if (count > 8) refusals.push({
|
|
953
|
-
code: "custody-unsupported-operation",
|
|
954
|
-
reason: "grant-batch-limit",
|
|
955
|
-
account,
|
|
956
|
-
count,
|
|
957
|
-
limit: 8
|
|
958
|
-
});
|
|
959
|
-
return {
|
|
960
|
-
isCreation,
|
|
961
|
-
issued,
|
|
962
|
-
burned,
|
|
963
|
-
refusals
|
|
964
|
-
};
|
|
965
|
-
};
|
|
966
|
-
/** The control input's checks: one control UTxO whose resolution holds a state. */
|
|
967
|
-
const readControlInput = (account, inputs) => {
|
|
968
|
-
const refusals = [];
|
|
969
|
-
const [control] = inputs.controls;
|
|
970
|
-
if (inputs.controls.length > 1) refusals.push({
|
|
971
|
-
code: "custody-context-mismatch",
|
|
972
|
-
reason: "multiple-control-inputs",
|
|
973
|
-
account
|
|
974
|
-
});
|
|
975
|
-
const before = control && require_outputs.decodedInlineState(control.output);
|
|
976
|
-
if (control) {
|
|
977
|
-
if (!before) refusals.push({
|
|
978
|
-
code: "custody-malformed-datum",
|
|
979
|
-
reason: "control-input-datum",
|
|
980
|
-
input: control.input
|
|
981
|
-
});
|
|
982
|
-
if (!require_outputs.holdsOnlyLovelaceAndStateNft(control.output.value, account)) refusals.push({
|
|
983
|
-
code: "custody-malformed-datum",
|
|
984
|
-
reason: "control-input-value",
|
|
985
|
-
input: control.input
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
return {
|
|
989
|
-
control,
|
|
990
|
-
before,
|
|
991
|
-
refusals
|
|
992
|
-
};
|
|
993
|
-
};
|
|
994
|
-
/**
|
|
995
|
-
* The account's control UTxO among the reference inputs. Only a grant spend
|
|
996
|
-
* reads the control UTxO without spending it: Lace spends it for every
|
|
997
|
-
* owner operation, delegation and withdrawal included.
|
|
998
|
-
*/
|
|
999
|
-
const readReferencedControls = (account, facts, isGrantSpend) => {
|
|
1000
|
-
const references = facts.referenceInputs.filter(({ output }) => require_outputs.isControlOutput(output, account));
|
|
1001
|
-
return {
|
|
1002
|
-
references,
|
|
1003
|
-
refusals: isGrantSpend ? [] : references.map(({ input }) => ({
|
|
1004
|
-
code: "custody-unsupported-operation",
|
|
1005
|
-
reason: "reference-control",
|
|
1006
|
-
input
|
|
1007
|
-
}))
|
|
1008
|
-
};
|
|
1009
|
-
};
|
|
1010
|
-
/**
|
|
1011
|
-
* Registration belongs to creation and creation needs it; delegation and
|
|
1012
|
-
* withdrawal need the control UTxO spent, since Lace never references it
|
|
1013
|
-
* for them.
|
|
1014
|
-
*/
|
|
1015
|
-
const checkStakeOperations = (account, { certificates, withdrawal }, { isCreation, hasControlInput }) => {
|
|
1016
|
-
const refusals = [];
|
|
1017
|
-
const isRegistered = certificates.some(({ kind }) => kind === "registration");
|
|
1018
|
-
if (isRegistered && !isCreation) refusals.push({
|
|
1019
|
-
code: "custody-unsupported-operation",
|
|
1020
|
-
reason: "registration-without-creation",
|
|
1021
|
-
account
|
|
1022
|
-
});
|
|
1023
|
-
if (isCreation && !isRegistered) refusals.push({
|
|
1024
|
-
code: "custody-unsupported-operation",
|
|
1025
|
-
reason: "creation-without-registration",
|
|
1026
|
-
account
|
|
1027
|
-
});
|
|
1028
|
-
if ((withdrawal !== void 0 || certificates.some(({ kind }) => kind === "pool-delegation")) && !hasControlInput) refusals.push({
|
|
1029
|
-
code: "custody-unsupported-operation",
|
|
1030
|
-
reason: "stake-operation-without-control",
|
|
1031
|
-
account
|
|
1032
|
-
});
|
|
1033
|
-
return refusals;
|
|
1034
|
-
};
|
|
1035
|
-
/**
|
|
1036
|
-
* The control output's checks. With a control input or a creation there is
|
|
1037
|
-
* exactly one, holding only lovelace and the NFT and an account state, with
|
|
1038
|
-
* no reference script, which the proxy refuses there; without either there
|
|
1039
|
-
* is none.
|
|
1040
|
-
*/
|
|
1041
|
-
const readControlOutput = (account, facts, hasStateSource) => {
|
|
1042
|
-
const refusals = [];
|
|
1043
|
-
const controls = facts.outputs.flatMap((view, outputIndex) => require_outputs.isControlOutput(view, account) ? [{
|
|
1044
|
-
view,
|
|
1045
|
-
outputIndex
|
|
1046
|
-
}] : []);
|
|
1047
|
-
if (!hasStateSource && controls.length > 0) refusals.push({
|
|
1048
|
-
code: "custody-context-mismatch",
|
|
1049
|
-
reason: "control-output-without-control-input",
|
|
1050
|
-
account
|
|
1051
|
-
});
|
|
1052
|
-
if (hasStateSource && controls.length !== 1) refusals.push({
|
|
1053
|
-
code: "custody-malformed-datum",
|
|
1054
|
-
reason: "control-output-count",
|
|
1055
|
-
account,
|
|
1056
|
-
count: controls.length
|
|
1057
|
-
});
|
|
1058
|
-
const control = controls.length === 1 ? controls[0] : void 0;
|
|
1059
|
-
const after = control && require_outputs.decodedInlineState(control.view);
|
|
1060
|
-
if (hasStateSource && control) {
|
|
1061
|
-
if (!require_outputs.holdsOnlyLovelaceAndStateNft(control.view.value, account)) refusals.push({
|
|
1062
|
-
code: "custody-malformed-datum",
|
|
1063
|
-
reason: "control-output-value",
|
|
1064
|
-
outputIndex: control.outputIndex
|
|
1065
|
-
});
|
|
1066
|
-
if (!after) refusals.push({
|
|
1067
|
-
code: "custody-malformed-datum",
|
|
1068
|
-
reason: "control-output-datum",
|
|
1069
|
-
outputIndex: control.outputIndex
|
|
1070
|
-
});
|
|
1071
|
-
if (control.view.hasReferenceScript) refusals.push({
|
|
1072
|
-
code: "custody-unsupported-operation",
|
|
1073
|
-
reason: "control-output-reference-script",
|
|
1074
|
-
outputIndex: control.outputIndex
|
|
1075
|
-
});
|
|
1076
|
-
}
|
|
1077
|
-
return {
|
|
1078
|
-
count: controls.length,
|
|
1079
|
-
control,
|
|
1080
|
-
after,
|
|
1081
|
-
refusals
|
|
1082
|
-
};
|
|
1083
|
-
};
|
|
1084
|
-
/** What one issued grant breaks of `issues_grant`, or its output and grant. */
|
|
1085
|
-
const readIssuedGrant = (account, facts, { assetId }) => {
|
|
1086
|
-
const holders = facts.outputs.flatMap((view, outputIndex) => view.value.assets.get(assetId) === 1n ? [{
|
|
1087
|
-
view,
|
|
1088
|
-
outputIndex
|
|
1089
|
-
}] : []);
|
|
1090
|
-
if (holders.length !== 1) return { problem: {
|
|
1091
|
-
code: "output-count",
|
|
1092
|
-
count: holders.length
|
|
1093
|
-
} };
|
|
1094
|
-
const [{ view, outputIndex }] = holders;
|
|
1095
|
-
if (!require_outputs.isAtAccount(view, account)) return { problem: {
|
|
1096
|
-
code: "address",
|
|
1097
|
-
outputIndex
|
|
1098
|
-
} };
|
|
1099
|
-
if (view.value.assets.size !== 1) return { problem: {
|
|
1100
|
-
code: "value",
|
|
1101
|
-
outputIndex
|
|
1102
|
-
} };
|
|
1103
|
-
if (view.hasReferenceScript) return { problem: {
|
|
1104
|
-
code: "reference-script",
|
|
1105
|
-
outputIndex
|
|
1106
|
-
} };
|
|
1107
|
-
if (view.inlineDatum === void 0) return { problem: {
|
|
1108
|
-
code: "datum-not-inline",
|
|
1109
|
-
outputIndex
|
|
1110
|
-
} };
|
|
1111
|
-
const decoded = require_outputs.decodeCustodyGrant(view.inlineDatum);
|
|
1112
|
-
return decoded.ok ? {
|
|
1113
|
-
view,
|
|
1114
|
-
outputIndex,
|
|
1115
|
-
grant: decoded.value
|
|
1116
|
-
} : { problem: {
|
|
1117
|
-
code: "datum-malformed",
|
|
1118
|
-
outputIndex
|
|
1119
|
-
} };
|
|
1120
|
-
};
|
|
1121
|
-
/**
|
|
1122
|
-
* The grants a device rewrite issues: `issues_grant` for each minted token,
|
|
1123
|
-
* in slot order from the spent state's next slot, each under the generation
|
|
1124
|
-
* of the control output, plus the rules Lace applies to every grant it
|
|
1125
|
-
* authors and an expiry after `now`.
|
|
1126
|
-
*/
|
|
1127
|
-
const readIssuedGrants = (account, facts, { issued, before, after, now }) => {
|
|
1128
|
-
const refusals = [];
|
|
1129
|
-
const reports = [];
|
|
1130
|
-
const refuse = (slot, problem) => refusals.push({
|
|
1131
|
-
code: "custody-malformed-datum",
|
|
1132
|
-
reason: "grant-output-invalid",
|
|
1133
|
-
account,
|
|
1134
|
-
slot,
|
|
1135
|
-
problem
|
|
1136
|
-
});
|
|
1137
|
-
for (const [index, mint] of issued.entries()) {
|
|
1138
|
-
const { slot } = mint;
|
|
1139
|
-
if (before) {
|
|
1140
|
-
const expected = before.nextSlot + BigInt(index);
|
|
1141
|
-
if (slot !== expected) refuse(slot, {
|
|
1142
|
-
code: "not-next-slot",
|
|
1143
|
-
expected
|
|
1144
|
-
});
|
|
1145
|
-
}
|
|
1146
|
-
const read = readIssuedGrant(account, facts, mint);
|
|
1147
|
-
if ("problem" in read) {
|
|
1148
|
-
refuse(slot, read.problem);
|
|
1149
|
-
continue;
|
|
1150
|
-
}
|
|
1151
|
-
const { view, outputIndex, grant } = read;
|
|
1152
|
-
if (grant.slot !== slot) refuse(slot, {
|
|
1153
|
-
code: "slot-mismatch",
|
|
1154
|
-
outputIndex,
|
|
1155
|
-
datumSlot: grant.slot
|
|
1156
|
-
});
|
|
1157
|
-
if (after && grant.generation !== after.grantGeneration) refuse(slot, {
|
|
1158
|
-
code: "generation-mismatch",
|
|
1159
|
-
outputIndex,
|
|
1160
|
-
generation: grant.generation,
|
|
1161
|
-
expected: after.grantGeneration
|
|
1162
|
-
});
|
|
1163
|
-
refusals.push(...require_outputs.findCustodyGrantViolations(grant).map((violation) => ({
|
|
1164
|
-
code: "custody-malformed-datum",
|
|
1165
|
-
reason: "grant-violation",
|
|
1166
|
-
account,
|
|
1167
|
-
slot,
|
|
1168
|
-
violation
|
|
1169
|
-
})));
|
|
1170
|
-
if (require_outputs.isCustodyGrantExpired(grant, now)) refusals.push({
|
|
1171
|
-
code: "custody-malformed-datum",
|
|
1172
|
-
reason: "expired-grant",
|
|
1173
|
-
account,
|
|
1174
|
-
slot,
|
|
1175
|
-
expiresAt: grant.scope.expiresAt
|
|
1176
|
-
});
|
|
1177
|
-
reports.push({
|
|
1178
|
-
outputIndex,
|
|
1179
|
-
assetId: mint.assetId,
|
|
1180
|
-
grant,
|
|
1181
|
-
lovelace: view.value.lovelace
|
|
1182
|
-
});
|
|
1183
|
-
}
|
|
1184
|
-
return {
|
|
1185
|
-
reports,
|
|
1186
|
-
refusals
|
|
1187
|
-
};
|
|
1188
|
-
};
|
|
1189
|
-
/**
|
|
1190
|
-
* `is_dead` against the spent state: issued under an older generation, its
|
|
1191
|
-
* slot revoked, or the validity interval starting strictly after its expiry.
|
|
1192
|
-
*/
|
|
1193
|
-
const deathOf = (grant, state, lowerBoundMs) => {
|
|
1194
|
-
if (grant.generation < state.grantGeneration) return "superseded";
|
|
1195
|
-
if (state.revoked.includes(grant.slot)) return "revoked";
|
|
1196
|
-
return lowerBoundMs !== void 0 && Number.isSafeInteger(lowerBoundMs) && BigInt(lowerBoundMs) > grant.scope.expiresAt ? "expired" : void 0;
|
|
1197
|
-
};
|
|
1198
|
-
/**
|
|
1199
|
-
* The grants a device rewrite sweeps: each grant UTxO spent with
|
|
1200
|
-
* `SweepGrant` carries its grant inline beside only its token, is dead
|
|
1201
|
-
* against the spent state, and has its token burned; every burned token is
|
|
1202
|
-
* a swept grant's.
|
|
1203
|
-
*/
|
|
1204
|
-
const readSweptGrants = (account, facts, { sweeps, burned, before, hasControlInput }) => {
|
|
1205
|
-
const refusals = [];
|
|
1206
|
-
const reports = [];
|
|
1207
|
-
if (sweeps.length > 0 && !hasControlInput) refusals.push({
|
|
1208
|
-
code: "custody-unsupported-operation",
|
|
1209
|
-
reason: "sweep-without-control",
|
|
1210
|
-
account
|
|
1211
|
-
});
|
|
1212
|
-
for (const { input, output } of sweeps) {
|
|
1213
|
-
const grant = require_outputs.decodedInlineGrant(output);
|
|
1214
|
-
if (!grant) {
|
|
1215
|
-
refusals.push({
|
|
1216
|
-
code: "custody-malformed-datum",
|
|
1217
|
-
reason: "grant-input-datum",
|
|
1218
|
-
input
|
|
1219
|
-
});
|
|
1220
|
-
continue;
|
|
1221
|
-
}
|
|
1222
|
-
const assetId = require_outputs.grantAssetIdOf(account, grant.slot);
|
|
1223
|
-
if (!assetId || !require_outputs.holdsOnlyLovelaceAndGrantToken(output.value, account, grant.slot)) {
|
|
1224
|
-
refusals.push({
|
|
1225
|
-
code: "custody-malformed-datum",
|
|
1226
|
-
reason: "grant-input-value",
|
|
1227
|
-
input
|
|
1228
|
-
});
|
|
1229
|
-
continue;
|
|
1230
|
-
}
|
|
1231
|
-
if (facts.mint.get(assetId) !== -1n) refusals.push({
|
|
1232
|
-
code: "custody-unsupported-operation",
|
|
1233
|
-
reason: "sweep-burn-mismatch",
|
|
1234
|
-
account,
|
|
1235
|
-
slot: grant.slot
|
|
1236
|
-
});
|
|
1237
|
-
if (!before) continue;
|
|
1238
|
-
const death = deathOf(grant, before, facts.lowerBoundMs);
|
|
1239
|
-
if (!death) {
|
|
1240
|
-
refusals.push({
|
|
1241
|
-
code: "custody-unsupported-operation",
|
|
1242
|
-
reason: "sweep-not-dead",
|
|
1243
|
-
account,
|
|
1244
|
-
input,
|
|
1245
|
-
slot: grant.slot
|
|
1246
|
-
});
|
|
1247
|
-
continue;
|
|
1248
|
-
}
|
|
1249
|
-
reports.push({
|
|
1250
|
-
input,
|
|
1251
|
-
assetId,
|
|
1252
|
-
grant,
|
|
1253
|
-
lovelace: output.value.lovelace,
|
|
1254
|
-
death
|
|
1255
|
-
});
|
|
1256
|
-
}
|
|
1257
|
-
const sweptSlots = sweeps.flatMap(({ output }) => {
|
|
1258
|
-
const grant = require_outputs.decodedInlineGrant(output);
|
|
1259
|
-
return grant ? [grant.slot] : [];
|
|
1260
|
-
});
|
|
1261
|
-
for (const { slot } of burned) if (!sweptSlots.includes(slot)) refusals.push({
|
|
1262
|
-
code: "custody-unsupported-operation",
|
|
1263
|
-
reason: "sweep-burn-mismatch",
|
|
1264
|
-
account,
|
|
1265
|
-
slot
|
|
1266
|
-
});
|
|
1267
|
-
return {
|
|
1268
|
-
reports,
|
|
1269
|
-
refusals
|
|
1270
|
-
};
|
|
1271
|
-
};
|
|
1272
|
-
/**
|
|
1273
|
-
* Every control state the transaction spends, references or writes for the
|
|
1274
|
-
* account names a logic of its contract version's catalogue: the logic is
|
|
1275
|
-
* the account's rule set, and any registered script may be named there, so
|
|
1276
|
-
* an unknown one runs rules Lace has not reviewed. Each unknown logic is
|
|
1277
|
-
* refused once.
|
|
1278
|
-
*/
|
|
1279
|
-
const checkKnownLogics = (account, version, states) => {
|
|
1280
|
-
const unknown = /* @__PURE__ */ new Set();
|
|
1281
|
-
for (const state of states) if (state && !require_hex.findCustodyLogic(version, state.logic).ok) unknown.add(state.logic);
|
|
1282
|
-
return [...unknown].map((logic) => ({
|
|
1283
|
-
code: "custody-unsupported-version",
|
|
1284
|
-
reason: "unknown-logic",
|
|
1285
|
-
account,
|
|
1286
|
-
logic
|
|
1287
|
-
}));
|
|
1288
|
-
};
|
|
1289
|
-
/**
|
|
1290
|
-
* The counter rules of `recreates_control_output`, and the rules Lace adds:
|
|
1291
|
-
* the logic stays, since Lace does not sign an upgrade; a slot leaves the
|
|
1292
|
-
* revoked list only with its grant token burned in the same rewrite, or
|
|
1293
|
-
* with a generation step that supersedes every grant; a slot joins it only
|
|
1294
|
-
* once, and only after it was issued.
|
|
1295
|
-
*/
|
|
1296
|
-
const checkStateChange = (account, change, { issued, burned }) => {
|
|
1297
|
-
const { before, after } = change;
|
|
1298
|
-
const refusals = [];
|
|
1299
|
-
if (change.logic) refusals.push({
|
|
1300
|
-
code: "custody-unsupported-operation",
|
|
1301
|
-
reason: "logic-changed",
|
|
1302
|
-
account,
|
|
1303
|
-
before: change.logic.before,
|
|
1304
|
-
after: change.logic.after
|
|
1305
|
-
});
|
|
1306
|
-
if (after.grantGeneration < before.grantGeneration) refusals.push({
|
|
1307
|
-
code: "custody-malformed-datum",
|
|
1308
|
-
reason: "generation-decreased",
|
|
1309
|
-
account,
|
|
1310
|
-
before: before.grantGeneration,
|
|
1311
|
-
after: after.grantGeneration
|
|
1312
|
-
});
|
|
1313
|
-
const minted = BigInt(issued.length);
|
|
1314
|
-
const counters = [[
|
|
1315
|
-
"nextSlot",
|
|
1316
|
-
before.nextSlot + minted,
|
|
1317
|
-
after.nextSlot
|
|
1318
|
-
], [
|
|
1319
|
-
"outstanding",
|
|
1320
|
-
before.outstanding + minted - BigInt(burned.length),
|
|
1321
|
-
after.outstanding
|
|
1322
|
-
]];
|
|
1323
|
-
for (const [counter, expected, actual] of counters) if (actual !== expected) refusals.push({
|
|
1324
|
-
code: "custody-malformed-datum",
|
|
1325
|
-
reason: "counter-mismatch",
|
|
1326
|
-
account,
|
|
1327
|
-
counter,
|
|
1328
|
-
expected,
|
|
1329
|
-
actual
|
|
1330
|
-
});
|
|
1331
|
-
if (after.grantGeneration === before.grantGeneration) {
|
|
1332
|
-
for (const slot of change.revokedRemoved) if (!burned.some((mint) => mint.slot === slot)) refusals.push({
|
|
1333
|
-
code: "custody-unsupported-operation",
|
|
1334
|
-
reason: "revoked-slot-restored",
|
|
1335
|
-
account,
|
|
1336
|
-
slot
|
|
1337
|
-
});
|
|
1338
|
-
}
|
|
1339
|
-
const invalid = change.revokedAdded.filter((slot, index) => slot < 0n || slot >= before.nextSlot || change.revokedAdded.indexOf(slot) !== index);
|
|
1340
|
-
for (const slot of new Set(after.revoked)) {
|
|
1341
|
-
const previousCount = before.revoked.filter((value) => value === slot).length;
|
|
1342
|
-
if (after.revoked.filter((value) => value === slot).length > Math.max(1, previousCount)) invalid.push(slot);
|
|
1343
|
-
}
|
|
1344
|
-
for (const slot of new Set(invalid)) refusals.push({
|
|
1345
|
-
code: "custody-unsupported-operation",
|
|
1346
|
-
reason: "revoked-slot-invalid",
|
|
1347
|
-
account,
|
|
1348
|
-
slot
|
|
1349
|
-
});
|
|
1350
|
-
return refusals;
|
|
1351
|
-
};
|
|
1352
|
-
/**
|
|
1353
|
-
* Grant spends are refused unless the caller allows them. An allowed one is
|
|
1354
|
-
* refused for every rule of the logic's grant spend rule it breaks, so an
|
|
1355
|
-
* accepted grant spend is one the contract accepts, and for any input
|
|
1356
|
-
* outside the account address: an agent spends from the account alone.
|
|
1357
|
-
*/
|
|
1358
|
-
const checkGrantSpends = ({ account, spends }, facts, scope) => [...spends.flatMap(({ input, output }) => scope.options.allowGrantSpend ? grantSpendViolationsOf({
|
|
1359
|
-
grantInput: output,
|
|
1360
|
-
inputs: facts.inputs.map((view) => view.output),
|
|
1361
|
-
referenceInputs: facts.referenceInputs.map((view) => view.output),
|
|
1362
|
-
outputs: facts.outputs,
|
|
1363
|
-
mint: facts.mint,
|
|
1364
|
-
requiredSigners: facts.requiredSigners,
|
|
1365
|
-
upperBoundMs: facts.upperBoundMs,
|
|
1366
|
-
account
|
|
1367
|
-
}).map((violation) => ({
|
|
1368
|
-
code: "custody-unsupported-operation",
|
|
1369
|
-
reason: "grant-spend-violation",
|
|
1370
|
-
account,
|
|
1371
|
-
input,
|
|
1372
|
-
violation
|
|
1373
|
-
})) : [{
|
|
1374
|
-
code: "custody-unsupported-operation",
|
|
1375
|
-
reason: "grant-spend",
|
|
1376
|
-
account,
|
|
1377
|
-
input
|
|
1378
|
-
}]), ...scope.options.allowGrantSpend && spends.length > 0 ? facts.inputs.filter(({ output }) => !require_outputs.isAtAccount(output, account)).map(({ input }) => ({
|
|
1379
|
-
code: "custody-unsupported-operation",
|
|
1380
|
-
reason: "grant-spend-foreign-input",
|
|
1381
|
-
account,
|
|
1382
|
-
input
|
|
1383
|
-
})) : []];
|
|
1384
|
-
/** The grant a single grant spend uses and the output that recreates it. */
|
|
1385
|
-
const grantSpendReportOf = (account, spends, facts) => {
|
|
1386
|
-
if (spends.length !== 1) return void 0;
|
|
1387
|
-
const [{ input, output }] = spends;
|
|
1388
|
-
const grant = require_outputs.decodedInlineGrant(output);
|
|
1389
|
-
const assetId = grant && require_outputs.grantAssetIdOf(account, grant.slot);
|
|
1390
|
-
const outputIndex = facts.outputs.findIndex((view) => assetId !== void 0 && view.value.assets.get(assetId) === 1n);
|
|
1391
|
-
const after = outputIndex === -1 ? void 0 : require_outputs.decodedInlineGrant(facts.outputs[outputIndex]);
|
|
1392
|
-
return grant && after ? {
|
|
1393
|
-
input,
|
|
1394
|
-
grant,
|
|
1395
|
-
outputIndex,
|
|
1396
|
-
after
|
|
1397
|
-
} : void 0;
|
|
1398
|
-
};
|
|
1399
|
-
/**
|
|
1400
|
-
* The operations a device rewrite performs, from its state change and the
|
|
1401
|
-
* grants it issues and sweeps. One revoked slot beside one issued grant is a
|
|
1402
|
-
* replacement; a generation step of one that empties the revoked list
|
|
1403
|
-
* under the same logic revokes every grant, where an upgrade has the same
|
|
1404
|
-
* counters.
|
|
1405
|
-
*/
|
|
1406
|
-
const stateOperationsOf = (change, issuedSlots, sweptSlots) => {
|
|
1407
|
-
const { before, after, revokedAdded } = change;
|
|
1408
|
-
const isRevokeAll = !change.logic && after.grantGeneration === before.grantGeneration + 1n && after.revoked.length === 0;
|
|
1409
|
-
const isReplace = !change.grantGeneration && revokedAdded.length === 1 && issuedSlots.length === 1;
|
|
1410
|
-
return [
|
|
1411
|
-
...isReplace ? [{
|
|
1412
|
-
kind: "replace-grant",
|
|
1413
|
-
slot: revokedAdded[0],
|
|
1414
|
-
issuedSlot: issuedSlots[0]
|
|
1415
|
-
}] : issuedSlots.map((slot) => ({
|
|
1416
|
-
kind: "issue-grant",
|
|
1417
|
-
slot
|
|
1418
|
-
})),
|
|
1419
|
-
...isRevokeAll ? [{ kind: "revoke-all-grants" }] : [...isReplace ? [] : revokedAdded.map((slot) => ({
|
|
1420
|
-
kind: "revoke-grant",
|
|
1421
|
-
slot
|
|
1422
|
-
})), ...change.grantGeneration ? [{ kind: "change-grant-generation" }] : []],
|
|
1423
|
-
...sweptSlots.length > 0 ? [{
|
|
1424
|
-
kind: "sweep-grants",
|
|
1425
|
-
slots: sweptSlots
|
|
1426
|
-
}] : [],
|
|
1427
|
-
...change.devicesAdded.map((device) => ({
|
|
1428
|
-
kind: "add-device",
|
|
1429
|
-
device
|
|
1430
|
-
})),
|
|
1431
|
-
...change.devicesRemoved.map((device) => ({
|
|
1432
|
-
kind: "remove-device",
|
|
1433
|
-
device
|
|
1434
|
-
})),
|
|
1435
|
-
...change.isReordered ? [{ kind: "reorder-state" }] : []
|
|
1436
|
-
];
|
|
1437
|
-
};
|
|
1438
|
-
/**
|
|
1439
|
-
* Lovelace leaving the account's address, its reward account and its logic
|
|
1440
|
-
* run's draw; negative is a net inflow. Lovelace is fungible, so what the
|
|
1441
|
-
* logic run draws is credited to the account: any of it that leaves counts
|
|
1442
|
-
* as the account's.
|
|
1443
|
-
*/
|
|
1444
|
-
const lovelaceGivenUp = ({ netOutflow, stake, logicRun }) => netOutflow.lovelace + (stake.withdrawal ?? 0n) + (logicRun?.draw ?? 0n);
|
|
1445
|
-
/**
|
|
1446
|
-
* Whether an owner operation pays value out of the account: any asset that
|
|
1447
|
-
* leaves it, or lovelace given up beyond its share of the fee. An account
|
|
1448
|
-
* that pays only its own fee spends nothing. Account tokens are minted and
|
|
1449
|
-
* burned, never paid out: placement keeps them at their accounts.
|
|
1450
|
-
*/
|
|
1451
|
-
const isOwnerSpend = (facts, feeShare) => [...facts.netOutflow.assets].some(([assetId, quantity]) => quantity > 0n && require_outputs.policyIdOf(assetId) !== facts.account.accountValidatorHash) || lovelaceGivenUp(facts) > feeShare;
|
|
1452
|
-
const operationsOf = (facts, feeShare) => {
|
|
1453
|
-
const { isCreation, grantSpend, stateChange, stake } = facts;
|
|
1454
|
-
return [
|
|
1455
|
-
...isCreation ? [{ kind: "create" }] : [],
|
|
1456
|
-
...grantSpend ? [{
|
|
1457
|
-
kind: "grant-spend",
|
|
1458
|
-
slot: grantSpend.grant.slot
|
|
1459
|
-
}] : [],
|
|
1460
|
-
...stateChange ? stateOperationsOf(stateChange, facts.issued.map(({ grant }) => grant.slot), facts.swept.map(({ grant }) => grant.slot)) : [],
|
|
1461
|
-
...!facts.isGrantSpend && isOwnerSpend(facts, feeShare) ? [{ kind: "owner-spend" }] : [],
|
|
1462
|
-
...stake.certificates.flatMap((certificate) => certificate.kind === "pool-delegation" ? [{
|
|
1463
|
-
kind: "delegate",
|
|
1464
|
-
poolId: certificate.poolId
|
|
1465
|
-
}] : []),
|
|
1466
|
-
...stake.withdrawal === void 0 ? [] : [{
|
|
1467
|
-
kind: "withdraw-rewards",
|
|
1468
|
-
quantity: stake.withdrawal
|
|
1469
|
-
}]
|
|
1470
|
-
];
|
|
1471
|
-
};
|
|
1472
|
-
/** Each operated account's part of the accounts' fee share, in order. */
|
|
1473
|
-
const feeSharesOfAccounts = (operated, { accountShare }) => accountFeeSharesOf(operated.map(({ operationFacts }) => lovelaceGivenUp(operationFacts)), accountShare);
|
|
1474
|
-
/** The reports of the operated accounts, each with its operations named. */
|
|
1475
|
-
const withOperations = (operated, feeShares) => {
|
|
1476
|
-
const shares = feeSharesOfAccounts(operated, feeShares);
|
|
1477
|
-
return operated.map(({ report, operationFacts }, index) => ({
|
|
1478
|
-
...report,
|
|
1479
|
-
operations: operationsOf(operationFacts, shares[index])
|
|
1480
|
-
}));
|
|
1481
|
-
};
|
|
1482
|
-
/**
|
|
1483
|
-
* A logic run that draws lovelace must leave it at the account. Lovelace
|
|
1484
|
-
* is fungible, so the draw is credited to the account, and it stays there
|
|
1485
|
-
* when the account gives up no more lovelace than its part of the fee. A
|
|
1486
|
-
* fee payer that takes change back could have paid the whole fee, so the
|
|
1487
|
-
* accounts' part is then nothing: otherwise the payer's change could hold
|
|
1488
|
-
* the draw. A grant spend pays out of the account by definition, so it may
|
|
1489
|
-
* draw nothing.
|
|
1490
|
-
*/
|
|
1491
|
-
const checkLogicDraws = (operated, feeShares, isChangeToPayers) => {
|
|
1492
|
-
const shares = isChangeToPayers ? operated.map(() => 0n) : feeSharesOfAccounts(operated, feeShares);
|
|
1493
|
-
return operated.flatMap(({ operationFacts }, index) => {
|
|
1494
|
-
const { account, logicRun, isGrantSpend } = operationFacts;
|
|
1495
|
-
if (!logicRun || logicRun.draw === 0n) return [];
|
|
1496
|
-
return isGrantSpend || lovelaceGivenUp(operationFacts) > shares[index] ? [{
|
|
1497
|
-
code: "custody-unsupported-operation",
|
|
1498
|
-
reason: "logic-draw-leaves-account",
|
|
1499
|
-
account,
|
|
1500
|
-
logic: logicRun.logic,
|
|
1501
|
-
quantity: logicRun.draw
|
|
1502
|
-
}] : [];
|
|
1503
|
-
});
|
|
1504
|
-
};
|
|
1505
|
-
const isOwnReserve = (view) => view.inlineDatum !== void 0 && !view.hasReferenceScript && isCustodyReserveDatum(view.inlineDatum);
|
|
1506
|
-
const inspectAccount = (candidate, facts, scope) => {
|
|
1507
|
-
const account = hashesOf(candidate);
|
|
1508
|
-
const { networkId } = scope.options.chainId;
|
|
1509
|
-
const inputs = readAccountInputs(account, facts);
|
|
1510
|
-
const { control, before, ...controlInput } = readControlInput(account, inputs);
|
|
1511
|
-
const hasControlInput = control !== void 0;
|
|
1512
|
-
const isGrantSpend = inputs.spends.length > 0;
|
|
1513
|
-
const mint = readMint(account, facts, hasControlInput);
|
|
1514
|
-
const stake = readStakeEffects(candidate, facts);
|
|
1515
|
-
const controlOutput = readControlOutput(account, facts, hasControlInput || mint.isCreation);
|
|
1516
|
-
const { after } = controlOutput;
|
|
1517
|
-
const referenced = readReferencedControls(account, facts, isGrantSpend);
|
|
1518
|
-
const referencedStates = referenced.references.map(({ output }) => require_outputs.decodedInlineState(output));
|
|
1519
|
-
const states = [
|
|
1520
|
-
before,
|
|
1521
|
-
after,
|
|
1522
|
-
...referencedStates
|
|
1523
|
-
];
|
|
1524
|
-
const logicRun = readLogicRun(account, facts, before?.logic ?? (isGrantSpend ? referencedStates[0]?.logic : void 0) ?? (mint.isCreation ? after?.logic : void 0));
|
|
1525
|
-
const issued = readIssuedGrants(account, facts, {
|
|
1526
|
-
issued: mint.issued,
|
|
1527
|
-
before,
|
|
1528
|
-
after,
|
|
1529
|
-
now: scope.options.now
|
|
1530
|
-
});
|
|
1531
|
-
const swept = readSweptGrants(account, facts, {
|
|
1532
|
-
sweeps: inputs.sweeps,
|
|
1533
|
-
burned: mint.burned,
|
|
1534
|
-
before,
|
|
1535
|
-
hasControlInput
|
|
1536
|
-
});
|
|
1537
|
-
const refusals = [
|
|
1538
|
-
...inputs.refusals,
|
|
1539
|
-
...controlInput.refusals,
|
|
1540
|
-
...mint.refusals,
|
|
1541
|
-
...referenced.refusals,
|
|
1542
|
-
...checkKnownLogics(account, candidate.contractVersion, states),
|
|
1543
|
-
...logicRun.refusals,
|
|
1544
|
-
...stake.refusals,
|
|
1545
|
-
...checkStakeOperations(account, stake, {
|
|
1546
|
-
isCreation: mint.isCreation,
|
|
1547
|
-
hasControlInput
|
|
1548
|
-
}),
|
|
1549
|
-
...controlOutput.refusals,
|
|
1550
|
-
...issued.refusals,
|
|
1551
|
-
...swept.refusals,
|
|
1552
|
-
...checkGrantSpends({
|
|
1553
|
-
account,
|
|
1554
|
-
spends: inputs.spends
|
|
1555
|
-
}, facts, scope)
|
|
1556
|
-
];
|
|
1557
|
-
const netOutflow = leavingValueAt(facts.inputs.map(({ output }) => output), facts.outputs, require_outputs.accountPlutusAddressKey(account));
|
|
1558
|
-
let stateChange;
|
|
1559
|
-
if (after && (before || mint.isCreation)) {
|
|
1560
|
-
const written = {
|
|
1561
|
-
account,
|
|
1562
|
-
before,
|
|
1563
|
-
after
|
|
1564
|
-
};
|
|
1565
|
-
refusals.push(...checkWrittenState(written), ...checkSigner(written, facts, scope));
|
|
1566
|
-
if (before) {
|
|
1567
|
-
stateChange = diffCustodyStates(before, after);
|
|
1568
|
-
refusals.push(...checkStateChange(account, stateChange, mint));
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
const [reference] = referenced.references;
|
|
1572
|
-
const [referencedState] = referencedStates;
|
|
1573
|
-
const grantSpend = grantSpendReportOf(account, inputs.spends, facts);
|
|
1574
|
-
return {
|
|
1575
|
-
isOperated: inputs.isSpent || mint.isCreation || mint.issued.length + mint.burned.length > 0 || stake.isStaked || controlOutput.count > 0,
|
|
1576
|
-
refusals,
|
|
1577
|
-
logics: states.flatMap((state) => state ? [state.logic] : []),
|
|
1578
|
-
report: {
|
|
1579
|
-
contractVersion: candidate.contractVersion,
|
|
1580
|
-
...account,
|
|
1581
|
-
address: require_outputs.accountAddressOf(account, networkId),
|
|
1582
|
-
rewardAccount: require_outputs.accountRewardAccountOf(account, networkId),
|
|
1583
|
-
...control && before ? { controlInput: {
|
|
1584
|
-
input: control.input,
|
|
1585
|
-
state: before,
|
|
1586
|
-
lovelace: control.output.value.lovelace
|
|
1587
|
-
} } : {},
|
|
1588
|
-
...controlOutput.control && after ? { controlOutput: {
|
|
1589
|
-
outputIndex: controlOutput.control.outputIndex,
|
|
1590
|
-
state: after,
|
|
1591
|
-
lovelace: controlOutput.control.view.value.lovelace
|
|
1592
|
-
} } : {},
|
|
1593
|
-
...isGrantSpend && reference && referencedState ? { referencedControl: {
|
|
1594
|
-
input: reference.input,
|
|
1595
|
-
state: referencedState,
|
|
1596
|
-
lovelace: reference.output.value.lovelace
|
|
1597
|
-
} } : {},
|
|
1598
|
-
fundInputs: inputs.funds.map(({ input, output }) => ({
|
|
1599
|
-
input,
|
|
1600
|
-
value: output.txOut.value,
|
|
1601
|
-
hasReferenceScript: output.hasReferenceScript
|
|
1602
|
-
})),
|
|
1603
|
-
reserveInputs: inputs.reserves.map(({ input, output }) => ({
|
|
1604
|
-
input,
|
|
1605
|
-
value: output.txOut.value,
|
|
1606
|
-
datum: output.datumKind === "hash" ? "hash" : "inline",
|
|
1607
|
-
isOwnFormat: isOwnReserve(output),
|
|
1608
|
-
hasReferenceScript: output.hasReferenceScript
|
|
1609
|
-
})),
|
|
1610
|
-
accountOutputs: facts.outputs.flatMap((view, outputIndex) => require_outputs.isAtAccount(view, account) ? [{
|
|
1611
|
-
outputIndex,
|
|
1612
|
-
value: view.txOut.value,
|
|
1613
|
-
datum: view.datumKind,
|
|
1614
|
-
kind: require_outputs.accountUtxoKindOf(view, account)
|
|
1615
|
-
}] : []),
|
|
1616
|
-
issuedGrants: issued.reports,
|
|
1617
|
-
sweptGrants: swept.reports,
|
|
1618
|
-
...grantSpend ? { grantSpend } : {},
|
|
1619
|
-
netOutflow,
|
|
1620
|
-
certificates: stake.certificates,
|
|
1621
|
-
...stake.withdrawal === void 0 ? {} : { withdrawal: stake.withdrawal },
|
|
1622
|
-
...logicRun.run && logicRun.run.draw > 0n ? { logicDraw: logicRun.run.draw } : {},
|
|
1623
|
-
...stateChange ? { stateChange } : {}
|
|
1624
|
-
},
|
|
1625
|
-
operationFacts: {
|
|
1626
|
-
account,
|
|
1627
|
-
isCreation: mint.isCreation,
|
|
1628
|
-
isGrantSpend,
|
|
1629
|
-
grantSpend,
|
|
1630
|
-
stateChange,
|
|
1631
|
-
issued: issued.reports,
|
|
1632
|
-
swept: swept.reports,
|
|
1633
|
-
netOutflow,
|
|
1634
|
-
stake,
|
|
1635
|
-
logicRun: logicRun.run
|
|
1636
|
-
}
|
|
1637
|
-
};
|
|
1638
|
-
};
|
|
1639
|
-
//#endregion
|
|
1640
|
-
//#region ../../packages/lib/cardano-custody/src/analysis/auxiliary-data.ts
|
|
1641
|
-
/**
|
|
1642
|
-
* The metadata a transaction carries, read from its auxiliary data and
|
|
1643
|
-
* bound to the signed body by the auxiliary data hash. Undefined without
|
|
1644
|
-
* any. A body that commits to auxiliary data the transaction does not
|
|
1645
|
-
* carry, or auxiliary data the body does not commit to by that hash, is
|
|
1646
|
-
* refused: a signature over the body would then authorise metadata consent
|
|
1647
|
-
* never saw. So is auxiliary data holding anything but metadata.
|
|
1648
|
-
*/
|
|
1649
|
-
const readCustodyMetadata = (transaction, body) => {
|
|
1650
|
-
let auxiliaryData;
|
|
1651
|
-
let carried;
|
|
1652
|
-
try {
|
|
1653
|
-
const serialized = transaction.auxiliaryData();
|
|
1654
|
-
auxiliaryData = serialized?.toCore();
|
|
1655
|
-
carried = serialized ? _laceio_cardano_crypto.blake2b.hash(serialized.toCbor(), 32) : void 0;
|
|
1656
|
-
} catch {
|
|
1657
|
-
return require_hex.fail({
|
|
1658
|
-
code: "custody-unsupported-operation",
|
|
1659
|
-
reason: "auxiliary-data-unreadable"
|
|
1660
|
-
});
|
|
1661
|
-
}
|
|
1662
|
-
if (body.auxiliaryDataHash?.toLowerCase() !== carried) return require_hex.fail({
|
|
1663
|
-
code: "custody-unsupported-operation",
|
|
1664
|
-
reason: "auxiliary-data-hash-mismatch"
|
|
1665
|
-
});
|
|
1666
|
-
if ((auxiliaryData?.scripts?.length ?? 0) > 0) return require_hex.fail({
|
|
1667
|
-
code: "custody-unsupported-operation",
|
|
1668
|
-
reason: "auxiliary-data-scripts"
|
|
1669
|
-
});
|
|
1670
|
-
const metadata = auxiliaryData?.blob;
|
|
1671
|
-
return require_hex.ok(metadata && metadata.size > 0 ? metadata : void 0);
|
|
1672
|
-
};
|
|
1673
|
-
/**
|
|
1674
|
-
* A grant spend carrying metadata. An agent spends only what its grant
|
|
1675
|
-
* allows, and attaching data to the account's transactions is not among it.
|
|
1676
|
-
*/
|
|
1677
|
-
const checkGrantSpendMetadata = (operated, metadata) => metadata === void 0 ? [] : operated.flatMap(({ report }) => report.grantSpend ? [{
|
|
1678
|
-
code: "custody-unsupported-operation",
|
|
1679
|
-
reason: "grant-spend-metadata",
|
|
1680
|
-
account: hashesOf(report),
|
|
1681
|
-
input: report.grantSpend.input
|
|
1682
|
-
}] : []);
|
|
1683
|
-
//#endregion
|
|
1684
|
-
//#region ../../packages/lib/cardano-custody/src/analysis/refusals.ts
|
|
1685
|
-
/**
|
|
1686
|
-
* The order checks run in: the record, the caller's context, the account
|
|
1687
|
-
* UTxOs it resolves, a grant spend, unsupported shapes, the written datums,
|
|
1688
|
-
* the signing device, the initial state at creation and locked outputs.
|
|
1689
|
-
*/
|
|
1690
|
-
const STAGE = {
|
|
1691
|
-
"unsupported-contract-version": 0,
|
|
1692
|
-
"invalid-record": 0,
|
|
1693
|
-
"unresolved-input": 1,
|
|
1694
|
-
"resolution-not-in-body": 1,
|
|
1695
|
-
"duplicate-resolution": 1,
|
|
1696
|
-
"invalid-resolution": 1,
|
|
1697
|
-
"network-mismatch": 1,
|
|
1698
|
-
"redeemer-mismatch": 1,
|
|
1699
|
-
"redeemer-on-key-input": 1,
|
|
1700
|
-
"reference-script-mismatch": 1,
|
|
1701
|
-
"unused-account-validator": 1,
|
|
1702
|
-
"unused-logic": 1,
|
|
1703
|
-
"multiple-control-inputs": 1,
|
|
1704
|
-
"foreign-account": 1,
|
|
1705
|
-
"account-not-operated": 1,
|
|
1706
|
-
"creation-without-record": 1,
|
|
1707
|
-
"control-output-without-control-input": 1,
|
|
1708
|
-
"control-input-datum": 2,
|
|
1709
|
-
"unknown-logic": 2,
|
|
1710
|
-
"control-input-value": 2,
|
|
1711
|
-
"grant-input-datum": 2,
|
|
1712
|
-
"grant-input-value": 2,
|
|
1713
|
-
"grant-spend": 3,
|
|
1714
|
-
"grant-spend-violation": 3,
|
|
1715
|
-
"grant-spend-foreign-input": 3,
|
|
1716
|
-
"grant-spend-metadata": 3,
|
|
1717
|
-
"unsupported-body-field": 4,
|
|
1718
|
-
"auxiliary-data-hash-mismatch": 4,
|
|
1719
|
-
"auxiliary-data-scripts": 4,
|
|
1720
|
-
"auxiliary-data-unreadable": 4,
|
|
1721
|
-
"missing-redeemer": 4,
|
|
1722
|
-
"unknown-redeemer": 4,
|
|
1723
|
-
"duplicate-redeemer": 4,
|
|
1724
|
-
"mint-redeemer-mismatch": 4,
|
|
1725
|
-
"foreign-certificate": 4,
|
|
1726
|
-
"refused-certificate": 4,
|
|
1727
|
-
"unsupported-certificate": 4,
|
|
1728
|
-
"foreign-withdrawal": 4,
|
|
1729
|
-
"duplicate-withdrawal": 4,
|
|
1730
|
-
"logic-draw-leaves-account": 4,
|
|
1731
|
-
"logic-withdrawal-missing": 4,
|
|
1732
|
-
"foreign-mint": 4,
|
|
1733
|
-
"unsupported-mint": 4,
|
|
1734
|
-
"issue-sweep-mixed": 4,
|
|
1735
|
-
"grant-batch-limit": 4,
|
|
1736
|
-
"grant-mint-without-control": 4,
|
|
1737
|
-
"reference-control": 4,
|
|
1738
|
-
"foreign-reference-script": 4,
|
|
1739
|
-
"creation-without-registration": 4,
|
|
1740
|
-
"fund-without-account-token": 4,
|
|
1741
|
-
"reserve-without-control": 4,
|
|
1742
|
-
"sweep-without-control": 4,
|
|
1743
|
-
"sweep-burn-mismatch": 4,
|
|
1744
|
-
"registration-without-creation": 4,
|
|
1745
|
-
"stake-operation-without-control": 4,
|
|
1746
|
-
"account-token-misplaced": 4,
|
|
1747
|
-
"foreign-reserve": 4,
|
|
1748
|
-
"deposit-reference-script": 4,
|
|
1749
|
-
"control-output-count": 5,
|
|
1750
|
-
"control-output-datum": 5,
|
|
1751
|
-
"control-output-value": 5,
|
|
1752
|
-
"control-output-reference-script": 5,
|
|
1753
|
-
"state-violation": 5,
|
|
1754
|
-
"counter-mismatch": 5,
|
|
1755
|
-
"generation-decreased": 5,
|
|
1756
|
-
"logic-changed": 5,
|
|
1757
|
-
"revoked-slot-restored": 5,
|
|
1758
|
-
"revoked-slot-invalid": 5,
|
|
1759
|
-
"grant-output-invalid": 5,
|
|
1760
|
-
"grant-violation": 5,
|
|
1761
|
-
"expired-grant": 5,
|
|
1762
|
-
"sweep-not-dead": 5,
|
|
1763
|
-
"no-device-signer": 6,
|
|
1764
|
-
"not-creator": 6,
|
|
1765
|
-
"not-listed": 6,
|
|
1766
|
-
"not-required-signer": 6,
|
|
1767
|
-
"initial-state": 7,
|
|
1768
|
-
"datum-hash": 8,
|
|
1769
|
-
"unspendable-address": 8
|
|
1770
|
-
};
|
|
1771
|
-
/** Refusals in check order; refusals of one stage keep the order found. */
|
|
1772
|
-
const inCheckOrder = (refusals) => refusals.map((refusal, position) => ({
|
|
1773
|
-
refusal,
|
|
1774
|
-
position
|
|
1775
|
-
})).sort((a, b) => STAGE[a.refusal.reason] - STAGE[b.refusal.reason] || a.position - b.position).map(({ refusal }) => refusal);
|
|
1776
|
-
//#endregion
|
|
1777
|
-
//#region ../../packages/lib/cardano-custody/src/analysis/transaction-checks.ts
|
|
1778
|
-
const UNSUPPORTED_BODY_FIELDS = [
|
|
1779
|
-
"accountBalanceIntervals",
|
|
1780
|
-
"directDeposits",
|
|
1781
|
-
"guards",
|
|
1782
|
-
"proposalProcedures",
|
|
1783
|
-
"subTransactions",
|
|
1784
|
-
"update",
|
|
1785
|
-
"votingProcedures"
|
|
1786
|
-
];
|
|
1787
|
-
/**
|
|
1788
|
-
* A redeemer pointer named twice has no single reading, so the redeemer a
|
|
1789
|
-
* check reads might not be the one a script runs with.
|
|
1790
|
-
*/
|
|
1791
|
-
const checkRedeemerPointers = (facts) => {
|
|
1792
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1793
|
-
const refusals = [];
|
|
1794
|
-
for (const { purpose, index } of facts.redeemers) {
|
|
1795
|
-
const pointer = `${purpose}:${index}`;
|
|
1796
|
-
if (seen.has(pointer)) refusals.push({
|
|
1797
|
-
code: "custody-unsupported-operation",
|
|
1798
|
-
reason: "duplicate-redeemer",
|
|
1799
|
-
purpose,
|
|
1800
|
-
index
|
|
1801
|
-
});
|
|
1802
|
-
seen.add(pointer);
|
|
1803
|
-
}
|
|
1804
|
-
return refusals;
|
|
1805
|
-
};
|
|
1806
|
-
/**
|
|
1807
|
-
* Consistency checks that catch resolutions hiding a custody spend. The
|
|
1808
|
-
* ledger refuses a spend redeemer for an input no script locks, and an
|
|
1809
|
-
* account validator witness that no input or mint needs, so either means a
|
|
1810
|
-
* resolution misstates what the transaction spends.
|
|
1811
|
-
*/
|
|
1812
|
-
const checkSpendWitnesses = (facts) => {
|
|
1813
|
-
const refusals = [];
|
|
1814
|
-
for (const [index, { input, output }] of facts.inputs.entries()) if (redeemerData(facts, _laceio_cardano_core.Cardano.RedeemerPurpose.spend, index) !== void 0 && paymentCredentialOf(output)?.type !== _laceio_cardano_core.Cardano.CredentialType.ScriptHash) refusals.push({
|
|
1815
|
-
code: "custody-context-mismatch",
|
|
1816
|
-
reason: "redeemer-on-key-input",
|
|
1817
|
-
input
|
|
1818
|
-
});
|
|
1819
|
-
for (const version of require_hex.SUPPORTED_CUSTODY_CONTRACT_VERSIONS) {
|
|
1820
|
-
const { accountValidatorHash } = version;
|
|
1821
|
-
const isNeeded = facts.inputs.some(({ output: { addressClass } }) => addressClass.kind === "account" && addressClass.contractVersion === version) || facts.mintPolicies.includes(accountValidatorHash);
|
|
1822
|
-
if (facts.witnessScriptHashes.has(accountValidatorHash) && !isNeeded) refusals.push({
|
|
1823
|
-
code: "custody-context-mismatch",
|
|
1824
|
-
reason: "unused-account-validator",
|
|
1825
|
-
accountValidatorHash
|
|
1826
|
-
});
|
|
1827
|
-
}
|
|
1828
|
-
return refusals;
|
|
1829
|
-
};
|
|
1830
|
-
/**
|
|
1831
|
-
* Consistency checks on the known logics a transaction attaches or runs: a
|
|
1832
|
-
* logic runs through a withdrawal from its stake credential, and only over
|
|
1833
|
-
* a control UTxO that names it, spent, referenced or written. The ledger
|
|
1834
|
-
* refuses a witness nothing needs, and a run with no such control UTxO
|
|
1835
|
-
* fails, so a logic attached without a run, or run with no control UTxO
|
|
1836
|
-
* naming it among `logics`, means a resolution hides that control UTxO or
|
|
1837
|
-
* the transaction cannot succeed.
|
|
1838
|
-
*/
|
|
1839
|
-
const checkLogicUse = (facts, logics) => {
|
|
1840
|
-
const named = new Set(logics);
|
|
1841
|
-
const withdrawn = new Set((facts.body.withdrawals ?? []).flatMap(({ stakeAddress }) => {
|
|
1842
|
-
const scriptHash = scriptHashOfRewardAccount(stakeAddress);
|
|
1843
|
-
return scriptHash === void 0 ? [] : [scriptHash];
|
|
1844
|
-
}));
|
|
1845
|
-
return [...new Set([...withdrawn, ...facts.witnessScriptHashes].filter(require_outputs.isKnownCustodyLogic))].filter((logic) => !withdrawn.has(logic) || !named.has(logic)).map((logic) => ({
|
|
1846
|
-
code: "custody-context-mismatch",
|
|
1847
|
-
reason: "unused-logic",
|
|
1848
|
-
logic
|
|
1849
|
-
}));
|
|
1850
|
-
};
|
|
1851
|
-
/**
|
|
1852
|
-
* A reward account the body's withdrawal map names more than once. The
|
|
1853
|
-
* decoded body keeps one of its withdrawals, so what inspection reads might
|
|
1854
|
-
* not be what the bytes say.
|
|
1855
|
-
*/
|
|
1856
|
-
const checkWithdrawalAccounts = (facts) => {
|
|
1857
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1858
|
-
const repeated = /* @__PURE__ */ new Set();
|
|
1859
|
-
for (const rewardAccount of facts.withdrawalAccounts) {
|
|
1860
|
-
if (seen.has(rewardAccount)) repeated.add(rewardAccount);
|
|
1861
|
-
seen.add(rewardAccount);
|
|
1862
|
-
}
|
|
1863
|
-
return [...repeated].map((rewardAccount) => ({
|
|
1864
|
-
code: "custody-unsupported-operation",
|
|
1865
|
-
reason: "duplicate-withdrawal",
|
|
1866
|
-
rewardAccount
|
|
1867
|
-
}));
|
|
1868
|
-
};
|
|
1869
|
-
const isSameOutpoint = (a, b) => a.txId.toLowerCase() === b.txId.toLowerCase() && a.index === b.index;
|
|
1870
|
-
/**
|
|
1871
|
-
* Reference inputs that carry a reference script must be the contract's
|
|
1872
|
-
* parked scripts on the inspected network: the output reference a supported
|
|
1873
|
-
* version pins, resolved with exactly the script it pins there. Any other
|
|
1874
|
-
* reference script is code Lace has not pinned.
|
|
1875
|
-
*/
|
|
1876
|
-
const checkReferenceScripts = (facts, networkMagic) => {
|
|
1877
|
-
const parked = require_hex.SUPPORTED_CUSTODY_CONTRACT_VERSIONS.flatMap((version) => version.referenceScripts[networkMagic] ?? []);
|
|
1878
|
-
return facts.referenceInputs.flatMap(({ input, output }) => {
|
|
1879
|
-
const pinned = parked.find(({ txIn }) => isSameOutpoint(txIn, input));
|
|
1880
|
-
if (pinned) return output.referenceScriptHash === pinned.scriptHash ? [] : [{
|
|
1881
|
-
code: "custody-context-mismatch",
|
|
1882
|
-
reason: "reference-script-mismatch",
|
|
1883
|
-
input
|
|
1884
|
-
}];
|
|
1885
|
-
return output.hasReferenceScript ? [{
|
|
1886
|
-
code: "custody-unsupported-operation",
|
|
1887
|
-
reason: "foreign-reference-script",
|
|
1888
|
-
input
|
|
1889
|
-
}] : [];
|
|
1890
|
-
});
|
|
1891
|
-
};
|
|
1892
|
-
/**
|
|
1893
|
-
* Certificates and withdrawals on credentials of no custody account the
|
|
1894
|
-
* transaction names. A withdrawal may also run a known logic, which
|
|
1895
|
-
* `checkLogicUse` ties to the control UTxOs naming it.
|
|
1896
|
-
*/
|
|
1897
|
-
const checkForeignStakeEffects = (facts, candidates, networkId) => {
|
|
1898
|
-
const refusals = [];
|
|
1899
|
-
const stakeScriptHashes = new Set(candidates.map(({ stakeScriptHash }) => stakeScriptHash));
|
|
1900
|
-
for (const [certificateIndex, certificate] of (facts.body.certificates ?? []).entries()) {
|
|
1901
|
-
const credential = stakeCredentialOf(certificate);
|
|
1902
|
-
if (credential?.type !== _laceio_cardano_core.Cardano.CredentialType.ScriptHash || !stakeScriptHashes.has(credential.hash)) refusals.push({
|
|
1903
|
-
code: "custody-unsupported-operation",
|
|
1904
|
-
reason: "foreign-certificate",
|
|
1905
|
-
certificateIndex,
|
|
1906
|
-
certificateType: certificate.__typename
|
|
1907
|
-
});
|
|
1908
|
-
}
|
|
1909
|
-
for (const { stakeAddress } of facts.body.withdrawals ?? []) {
|
|
1910
|
-
const scriptHash = scriptHashOfRewardAccount(stakeAddress);
|
|
1911
|
-
if (scriptHash === void 0 || !(stakeScriptHashes.has(scriptHash) || require_outputs.isKnownCustodyLogic(scriptHash))) refusals.push({
|
|
1912
|
-
code: "custody-unsupported-operation",
|
|
1913
|
-
reason: "foreign-withdrawal",
|
|
1914
|
-
rewardAccount: stakeAddress
|
|
1915
|
-
});
|
|
1916
|
-
else if (_laceio_cardano_core.Cardano.Address.fromString(stakeAddress)?.getNetworkId() !== networkId) refusals.push({
|
|
1917
|
-
code: "custody-context-mismatch",
|
|
1918
|
-
reason: "network-mismatch",
|
|
1919
|
-
address: stakeAddress
|
|
1920
|
-
});
|
|
1921
|
-
}
|
|
1922
|
-
return refusals;
|
|
1923
|
-
};
|
|
1924
|
-
/**
|
|
1925
|
-
* Mints of other policies, and custody mints of a shape no mint redeemer
|
|
1926
|
-
* accepts: one state NFT name, or grant names of one account that all issue
|
|
1927
|
-
* or all burn.
|
|
1928
|
-
*/
|
|
1929
|
-
const checkMint = (facts) => {
|
|
1930
|
-
const byPolicy = /* @__PURE__ */ new Map();
|
|
1931
|
-
for (const entry of facts.mint) {
|
|
1932
|
-
const policyId = require_outputs.policyIdOf(entry[0]);
|
|
1933
|
-
byPolicy.set(policyId, [...byPolicy.get(policyId) ?? [], entry]);
|
|
1934
|
-
}
|
|
1935
|
-
return [...byPolicy].flatMap(([policyId, entries]) => {
|
|
1936
|
-
const refuseEach = (reason) => entries.map(([assetId, quantity]) => ({
|
|
1937
|
-
code: "custody-unsupported-operation",
|
|
1938
|
-
reason,
|
|
1939
|
-
assetId,
|
|
1940
|
-
quantity
|
|
1941
|
-
}));
|
|
1942
|
-
if (!versionOfPolicy(policyId)) return refuseEach("foreign-mint");
|
|
1943
|
-
const shape = mintShapeOf(entries);
|
|
1944
|
-
if (shape.kind === "invalid") return refuseEach("unsupported-mint");
|
|
1945
|
-
return shape.kind === "mixed" ? [{
|
|
1946
|
-
code: "custody-unsupported-operation",
|
|
1947
|
-
reason: "issue-sweep-mixed",
|
|
1948
|
-
account: {
|
|
1949
|
-
accountValidatorHash: policyId,
|
|
1950
|
-
stakeScriptHash: shape.stakeScriptHash
|
|
1951
|
-
}
|
|
1952
|
-
}] : [];
|
|
1953
|
-
});
|
|
1954
|
-
};
|
|
1955
|
-
/**
|
|
1956
|
-
* What an output at a custody account that holds no account token must be:
|
|
1957
|
-
* a plain deposit, or a reserve in the format Lace writes, `d87980` with no
|
|
1958
|
-
* reference script. A datum hash locks value behind a preimage no account
|
|
1959
|
-
* builder holds; another datum or a reference script raises the fee and
|
|
1960
|
-
* minimum of every later spend.
|
|
1961
|
-
*/
|
|
1962
|
-
const findAccountOutputRefusal = (view, location) => {
|
|
1963
|
-
if (view.datumKind === "hash") return {
|
|
1964
|
-
code: "custody-locked-output",
|
|
1965
|
-
reason: "datum-hash",
|
|
1966
|
-
location
|
|
1967
|
-
};
|
|
1968
|
-
if (view.inlineDatum !== void 0) return view.hasReferenceScript || !isCustodyReserveDatum(view.inlineDatum) ? {
|
|
1969
|
-
code: "custody-unsupported-operation",
|
|
1970
|
-
reason: "foreign-reserve",
|
|
1971
|
-
location
|
|
1972
|
-
} : void 0;
|
|
1973
|
-
return view.hasReferenceScript ? {
|
|
1974
|
-
code: "custody-unsupported-operation",
|
|
1975
|
-
reason: "deposit-reference-script",
|
|
1976
|
-
location
|
|
1977
|
-
} : void 0;
|
|
1978
|
-
};
|
|
1979
|
-
/**
|
|
1980
|
-
* Rules over every output and resolution: custody addresses on the named
|
|
1981
|
-
* network, account tokens at their own account addresses, no value locked
|
|
1982
|
-
* where no redeemer can reach it, and account outputs without an account
|
|
1983
|
-
* token in a shape Lace writes. Outputs holding an account token are the
|
|
1984
|
-
* control and grant outputs, which their account's checks read.
|
|
1985
|
-
*/
|
|
1986
|
-
const checkOutputs = (facts, networkId) => {
|
|
1987
|
-
const refusals = [];
|
|
1988
|
-
const custodyViews = [
|
|
1989
|
-
...[
|
|
1990
|
-
...facts.inputs,
|
|
1991
|
-
...facts.referenceInputs,
|
|
1992
|
-
...facts.collaterals
|
|
1993
|
-
].map(({ output }) => output),
|
|
1994
|
-
...facts.outputs,
|
|
1995
|
-
...facts.collateralReturn ? [facts.collateralReturn] : []
|
|
1996
|
-
].filter(({ addressClass }) => addressClass.kind !== "other");
|
|
1997
|
-
for (const view of custodyViews) if (view.address?.getNetworkId() !== networkId) refusals.push({
|
|
1998
|
-
code: "custody-context-mismatch",
|
|
1999
|
-
reason: "network-mismatch",
|
|
2000
|
-
address: view.txOut.address
|
|
2001
|
-
});
|
|
2002
|
-
const accountValidatorHashes = require_hex.SUPPORTED_CUSTODY_CONTRACT_VERSIONS.map((version) => version.accountValidatorHash);
|
|
2003
|
-
for (const { input, output } of facts.inputs) if (require_outputs.holdsMisplacedAccountToken(output, accountValidatorHashes)) refusals.push({
|
|
2004
|
-
code: "custody-unsupported-operation",
|
|
2005
|
-
reason: "account-token-misplaced",
|
|
2006
|
-
location: {
|
|
2007
|
-
kind: "input",
|
|
2008
|
-
input
|
|
2009
|
-
}
|
|
2010
|
-
});
|
|
2011
|
-
const locatedOutputs = [...facts.outputs.map((view, index) => ({
|
|
2012
|
-
view,
|
|
2013
|
-
location: {
|
|
2014
|
-
kind: "output",
|
|
2015
|
-
index
|
|
2016
|
-
}
|
|
2017
|
-
})), ...facts.collateralReturn ? [{
|
|
2018
|
-
view: facts.collateralReturn,
|
|
2019
|
-
location: { kind: "collateral-return" }
|
|
2020
|
-
}] : []];
|
|
2021
|
-
for (const { view, location } of locatedOutputs) {
|
|
2022
|
-
if (location.kind === "output" && require_outputs.holdsMisplacedAccountToken(view, accountValidatorHashes)) refusals.push({
|
|
2023
|
-
code: "custody-unsupported-operation",
|
|
2024
|
-
reason: "account-token-misplaced",
|
|
2025
|
-
location
|
|
2026
|
-
});
|
|
2027
|
-
const { addressClass } = view;
|
|
2028
|
-
if (addressClass.kind === "unspendable") refusals.push({
|
|
2029
|
-
code: "custody-locked-output",
|
|
2030
|
-
reason: "unspendable-address",
|
|
2031
|
-
location
|
|
2032
|
-
});
|
|
2033
|
-
else if (addressClass.kind === "account" && require_outputs.holdsNoAccountToken(view.value, addressClass.contractVersion.accountValidatorHash)) {
|
|
2034
|
-
const refusal = findAccountOutputRefusal(view, location);
|
|
2035
|
-
if (refusal) refusals.push(refusal);
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
return refusals;
|
|
2039
|
-
};
|
|
2040
|
-
const checkBodyFields = (facts) => {
|
|
2041
|
-
const body = facts.body;
|
|
2042
|
-
return UNSUPPORTED_BODY_FIELDS.filter((field) => body[field] !== void 0).map((field) => ({
|
|
2043
|
-
code: "custody-unsupported-operation",
|
|
2044
|
-
reason: "unsupported-body-field",
|
|
2045
|
-
field
|
|
2046
|
-
}));
|
|
2047
|
-
};
|
|
2048
|
-
/** With a record, the transaction operates that account and no other. */
|
|
2049
|
-
const checkRecordAccount = (operated, record) => {
|
|
2050
|
-
if (!record) return [];
|
|
2051
|
-
const refusals = operated.filter((account) => !isSameAccount(account, record)).map((account) => ({
|
|
2052
|
-
code: "custody-context-mismatch",
|
|
2053
|
-
reason: "foreign-account",
|
|
2054
|
-
account: hashesOf(account)
|
|
2055
|
-
}));
|
|
2056
|
-
if (!operated.some((account) => isSameAccount(account, record))) refusals.push({
|
|
2057
|
-
code: "custody-context-mismatch",
|
|
2058
|
-
reason: "account-not-operated",
|
|
2059
|
-
account: {
|
|
2060
|
-
accountValidatorHash: record.contractVersion.accountValidatorHash,
|
|
2061
|
-
stakeScriptHash: record.stakeScriptHash
|
|
2062
|
-
}
|
|
2063
|
-
});
|
|
2064
|
-
return refusals;
|
|
2065
|
-
};
|
|
2066
|
-
//#endregion
|
|
2067
|
-
//#region ../../packages/lib/cardano-custody/src/analysis/inspect.ts
|
|
2068
|
-
const readRecord = (options) => {
|
|
2069
|
-
if (options.accountRecord === void 0) return require_hex.ok(void 0);
|
|
2070
|
-
const parsed = parseCustodyAccountRecord(options.accountRecord, options.chainId.networkMagic);
|
|
2071
|
-
if (parsed.ok) return require_hex.ok(parsed.value.identity);
|
|
2072
|
-
return require_hex.fail(parsed.error.code === "unsupported-contract-version" ? {
|
|
2073
|
-
code: "custody-unsupported-version",
|
|
2074
|
-
reason: "unsupported-contract-version",
|
|
2075
|
-
accountValidatorHash: parsed.error.accountValidatorHash
|
|
2076
|
-
} : {
|
|
2077
|
-
code: "custody-context-mismatch",
|
|
2078
|
-
reason: "invalid-record",
|
|
2079
|
-
recordError: parsed.error
|
|
2080
|
-
});
|
|
2081
|
-
};
|
|
2082
|
-
const paymentKeyHashOf = (view) => {
|
|
2083
|
-
const credential = paymentCredentialOf(view);
|
|
2084
|
-
return credential?.type === _laceio_cardano_core.Cardano.CredentialType.KeyHash ? credential.hash : void 0;
|
|
2085
|
-
};
|
|
2086
|
-
const externalInputOf = ({ input, output }, deviceKeyHash) => ({
|
|
2087
|
-
input,
|
|
2088
|
-
address: output.txOut.address,
|
|
2089
|
-
value: output.txOut.value,
|
|
2090
|
-
isDeviceKey: deviceKeyHash !== void 0 && paymentKeyHashOf(output) === deviceKeyHash
|
|
2091
|
-
});
|
|
2092
|
-
const validityOf = ({ validityInterval }, slotToPosixMs) => {
|
|
2093
|
-
const bounds = {};
|
|
2094
|
-
if (validityInterval?.invalidBefore !== void 0) {
|
|
2095
|
-
const slot = validityInterval.invalidBefore;
|
|
2096
|
-
bounds.invalidBefore = {
|
|
2097
|
-
slot,
|
|
2098
|
-
posixMs: slotToPosixMs(slot)
|
|
2099
|
-
};
|
|
2100
|
-
}
|
|
2101
|
-
if (validityInterval?.invalidHereafter !== void 0) {
|
|
2102
|
-
const slot = validityInterval.invalidHereafter;
|
|
2103
|
-
bounds.invalidHereafter = {
|
|
2104
|
-
slot,
|
|
2105
|
-
posixMs: slotToPosixMs(slot)
|
|
2106
|
-
};
|
|
2107
|
-
}
|
|
2108
|
-
return bounds;
|
|
2109
|
-
};
|
|
2110
|
-
/**
|
|
2111
|
-
* Reads every custody effect of a transaction from its bytes and the
|
|
2112
|
-
* caller's resolutions of its inputs, and refuses what Lace never signs.
|
|
2113
|
-
* The SDK checks its own builds with it before asking for a signature, and
|
|
2114
|
-
* the hosted signer derives its consent and its refusals from it, so both
|
|
2115
|
-
* apply one set of rules.
|
|
2116
|
-
*
|
|
2117
|
-
* It takes the `Serialization.Transaction` whose body hash is signed, so
|
|
2118
|
-
* what it reads is what a signature commits to. Resolutions come from the
|
|
2119
|
-
* caller and are not checked against the chain; an inline datum in a
|
|
2120
|
-
* resolution wins over a datum hash reported beside it. A custody account
|
|
2121
|
-
* is any address whose payment credential is a supported account validator
|
|
2122
|
-
* and whose stake credential is an inline script.
|
|
2123
|
-
*
|
|
2124
|
-
* Every operation on an account runs the logic its control datum names,
|
|
2125
|
-
* through a withdrawal from the logic's stake credential, and that logic
|
|
2126
|
-
* must be one of the contract version's catalogue. A reference input may
|
|
2127
|
-
* carry a reference script only as a contract script parked at the output
|
|
2128
|
-
* reference the version pins on the inspected network.
|
|
2129
|
-
*
|
|
2130
|
-
* Witness-set redeemers are bound to the signed body only through its
|
|
2131
|
-
* script data hash, which inspection does not recompute. A spend redeemer
|
|
2132
|
-
* must fit the UTxO it spends: `Device` the control UTxO, `SpendWithGrant`
|
|
2133
|
-
* or `SweepGrant` a grant UTxO, `Fund` a deposit. `SpendWithGrant` is
|
|
2134
|
-
* refused unless `allowGrantSpend` is set, and the mint redeemer must fit
|
|
2135
|
-
* the mint: `CreateAccount` a state NFT, `IssueGrants` grant tokens minted,
|
|
2136
|
-
* `BurnGrants` grant tokens burned. Another redeemer set under the same body
|
|
2137
|
-
* cannot change what the body does: it can only run a contract branch that
|
|
2138
|
-
* the device's signature does not authorise.
|
|
2139
|
-
*/
|
|
2140
|
-
const inspectCustodyTransaction = (transaction, resolvedInputs, options) => {
|
|
2141
|
-
const record = readRecord(options);
|
|
2142
|
-
if (!record.ok) return require_hex.fail({ refusals: [record.error] });
|
|
2143
|
-
const serializedBody = transaction.body();
|
|
2144
|
-
const body = serializedBody.toCore();
|
|
2145
|
-
const resolved = resolveBody(body, resolvedInputs);
|
|
2146
|
-
if (!resolved.ok) return require_hex.fail({ refusals: resolved.error });
|
|
2147
|
-
const metadata = readCustodyMetadata(transaction, body);
|
|
2148
|
-
if (!metadata.ok) return require_hex.fail({ refusals: [metadata.error] });
|
|
2149
|
-
const facts = readTransaction({
|
|
2150
|
-
body,
|
|
2151
|
-
bodyCbor: serializedBody.toCbor(),
|
|
2152
|
-
witness: transaction.witnessSet().toCore()
|
|
2153
|
-
}, resolved.value, options);
|
|
2154
|
-
const { networkId } = options.chainId;
|
|
2155
|
-
const scope = {
|
|
2156
|
-
options,
|
|
2157
|
-
record: record.value,
|
|
2158
|
-
deviceKeyHash: options.deviceKeyHash?.toLowerCase()
|
|
2159
|
-
};
|
|
2160
|
-
const candidates = findCandidates(facts, record.value);
|
|
2161
|
-
const outcomes = candidates.map((candidate) => inspectAccount(candidate, facts, scope));
|
|
2162
|
-
const operated = outcomes.filter(({ isOperated }) => isOperated);
|
|
2163
|
-
const isOperatedAccount = (view) => operated.some(({ report }) => require_outputs.isAtAccount(view, report));
|
|
2164
|
-
const externalInputs = facts.inputs.filter(({ output }) => !isOperatedAccount(output)).map((view) => externalInputOf(view, scope.deviceKeyHash));
|
|
2165
|
-
const externalOutputs = facts.outputs.flatMap((view, outputIndex) => view.addressClass.kind === "other" ? [{
|
|
2166
|
-
outputIndex,
|
|
2167
|
-
address: view.txOut.address,
|
|
2168
|
-
value: view.txOut.value
|
|
2169
|
-
}] : []);
|
|
2170
|
-
const feeShares = feeSharesOf({
|
|
2171
|
-
externalInputs,
|
|
2172
|
-
externalOutputs,
|
|
2173
|
-
fee: body.fee
|
|
2174
|
-
});
|
|
2175
|
-
const refusals = inCheckOrder([
|
|
2176
|
-
...checkBodyFields(facts),
|
|
2177
|
-
...checkWithdrawalAccounts(facts),
|
|
2178
|
-
...checkForeignStakeEffects(facts, candidates, networkId),
|
|
2179
|
-
...checkMint(facts),
|
|
2180
|
-
...checkRedeemerPointers(facts),
|
|
2181
|
-
...checkSpendWitnesses(facts),
|
|
2182
|
-
...checkLogicUse(facts, outcomes.flatMap(({ logics }) => logics)),
|
|
2183
|
-
...checkReferenceScripts(facts, options.chainId.networkMagic),
|
|
2184
|
-
...checkOutputs(facts, networkId),
|
|
2185
|
-
...checkRecordAccount(operated.map(({ report }) => report), record.value),
|
|
2186
|
-
...outcomes.flatMap((outcome) => outcome.refusals),
|
|
2187
|
-
...checkGrantSpendMetadata(operated, metadata.value),
|
|
2188
|
-
...checkLogicDraws(operated, feeShares, externalOutputs.some(({ address }) => feeShares.payers.includes(address)))
|
|
2189
|
-
]);
|
|
2190
|
-
if (refusals.length > 0) return require_hex.fail({ refusals });
|
|
2191
|
-
return require_hex.ok({
|
|
2192
|
-
accounts: withOperations(operated, feeShares),
|
|
2193
|
-
deposits: facts.outputs.flatMap((view, outputIndex) => view.addressClass.kind === "account" && view.datumKind !== "hash" && !isOperatedAccount(view) ? [{
|
|
2194
|
-
outputIndex,
|
|
2195
|
-
address: view.txOut.address,
|
|
2196
|
-
value: view.txOut.value,
|
|
2197
|
-
accountValidatorHash: view.addressClass.contractVersion.accountValidatorHash,
|
|
2198
|
-
stakeScriptHash: view.addressClass.stakeScriptHash,
|
|
2199
|
-
datum: view.datumKind
|
|
2200
|
-
}] : []),
|
|
2201
|
-
externalOutputs,
|
|
2202
|
-
externalInputs,
|
|
2203
|
-
collateralInputs: facts.collaterals.map((view) => externalInputOf(view, scope.deviceKeyHash)),
|
|
2204
|
-
...body.collateralReturn ? { collateralReturn: {
|
|
2205
|
-
address: body.collateralReturn.address,
|
|
2206
|
-
value: body.collateralReturn.value
|
|
2207
|
-
} } : {},
|
|
2208
|
-
requiredSigners: [...facts.requiredSigners],
|
|
2209
|
-
validity: validityOf(body, options.slotToPosixMs),
|
|
2210
|
-
fee: body.fee,
|
|
2211
|
-
feeShares,
|
|
2212
|
-
...body.donation === void 0 ? {} : { donation: body.donation },
|
|
2213
|
-
...metadata.value === void 0 ? {} : { metadata: metadata.value }
|
|
2214
|
-
});
|
|
2215
|
-
};
|
|
2216
|
-
//#endregion
|
|
2217
|
-
//#region ../../packages/lib/cardano-custody/src/analysis/requested-scope.ts
|
|
2218
|
-
/**
|
|
2219
|
-
* The recipient a bech32 Shelley payment address names, when the text is
|
|
2220
|
-
* spelled exactly as `toBech32` writes it: lower case, with the prefix of the
|
|
2221
|
-
* network its header names. Bech32 also decodes an upper-case spelling and
|
|
2222
|
-
* ignores the prefix, so a mainnet-looking address could otherwise stand for
|
|
2223
|
-
* a testnet recipient.
|
|
2224
|
-
*/
|
|
2225
|
-
const canonicalRecipientOf = (value) => {
|
|
2226
|
-
if (typeof value !== "string" || !_laceio_cardano_core.Cardano.Address.isValidBech32(value)) return;
|
|
2227
|
-
const address = _laceio_cardano_core.Cardano.Address.fromBech32(value);
|
|
2228
|
-
const recipient = require_outputs.custodyRecipientFromAddress(address);
|
|
2229
|
-
return recipient.ok && address.toBech32() === value ? {
|
|
2230
|
-
address,
|
|
2231
|
-
recipient: recipient.value
|
|
2232
|
-
} : void 0;
|
|
2233
|
-
};
|
|
2234
|
-
const recipientKeysOn = (recipients, networkId) => {
|
|
2235
|
-
const keys = [];
|
|
2236
|
-
for (const text of recipients) {
|
|
2237
|
-
const canonical = canonicalRecipientOf(text);
|
|
2238
|
-
if (canonical?.address.getNetworkId() !== networkId) return void 0;
|
|
2239
|
-
keys.push(require_outputs.recipientKey(canonical.recipient));
|
|
2240
|
-
}
|
|
2241
|
-
return keys;
|
|
2242
|
-
};
|
|
2243
|
-
const hasSameRecipients = (requested, actual, networkId) => {
|
|
2244
|
-
const requestedKeys = recipientKeysOn(requested, networkId);
|
|
2245
|
-
const { recipients } = actual.scope;
|
|
2246
|
-
return requestedKeys !== void 0 && requestedKeys.length === recipients.length && requestedKeys.every((key, index) => key === require_outputs.recipientKey(recipients[index]));
|
|
2247
|
-
};
|
|
2248
|
-
/**
|
|
2249
|
-
* The fields where a requested scope differs from a grant a transaction
|
|
2250
|
-
* issues, in declaration order; empty when they match. Every field must be
|
|
2251
|
-
* equal exactly. Recipients must be the same Plutus addresses in the same
|
|
2252
|
-
* order, and every requested recipient must be on the account network,
|
|
2253
|
-
* because the datum carries no network and the grant would otherwise send
|
|
2254
|
-
* to another network's address with the same credentials.
|
|
2255
|
-
*/
|
|
2256
|
-
const compareRequestedGrantScope = (requested, actual, chainId) => {
|
|
2257
|
-
const { scope } = actual;
|
|
2258
|
-
const isEqual = {
|
|
2259
|
-
slot: requested.slot === actual.slot,
|
|
2260
|
-
grantee: requested.grantee === actual.grantee,
|
|
2261
|
-
asset: requested.asset.policyId === scope.asset.policyId && requested.asset.assetName === scope.asset.assetName,
|
|
2262
|
-
perCallCap: requested.perCallCap === scope.perCallCap,
|
|
2263
|
-
cap: requested.cap === scope.cap,
|
|
2264
|
-
lovelacePerCallCap: requested.lovelacePerCallCap === scope.lovelacePerCallCap,
|
|
2265
|
-
lovelaceCap: requested.lovelaceCap === scope.lovelaceCap,
|
|
2266
|
-
expiresAt: requested.expiresAt === scope.expiresAt,
|
|
2267
|
-
recipients: hasSameRecipients(requested.recipients, actual, chainId.networkId)
|
|
2268
|
-
};
|
|
2269
|
-
return Object.keys(isEqual).filter((field) => !isEqual[field]);
|
|
2270
|
-
};
|
|
2271
|
-
const serializeCustodyRequestedGrantScope = (scope) => ({
|
|
2272
|
-
slot: scope.slot.toString(),
|
|
2273
|
-
grantee: scope.grantee,
|
|
2274
|
-
asset: {
|
|
2275
|
-
policyId: scope.asset.policyId,
|
|
2276
|
-
assetName: scope.asset.assetName
|
|
2277
|
-
},
|
|
2278
|
-
perCallCap: scope.perCallCap.toString(),
|
|
2279
|
-
cap: scope.cap.toString(),
|
|
2280
|
-
lovelacePerCallCap: scope.lovelacePerCallCap.toString(),
|
|
2281
|
-
lovelaceCap: scope.lovelaceCap.toString(),
|
|
2282
|
-
expiresAt: scope.expiresAt.toString(),
|
|
2283
|
-
recipients: [...scope.recipients]
|
|
2284
|
-
});
|
|
2285
|
-
const SCOPE_FIELDS = [
|
|
2286
|
-
"slot",
|
|
2287
|
-
"grantee",
|
|
2288
|
-
"asset",
|
|
2289
|
-
"perCallCap",
|
|
2290
|
-
"cap",
|
|
2291
|
-
"lovelacePerCallCap",
|
|
2292
|
-
"lovelaceCap",
|
|
2293
|
-
"expiresAt",
|
|
2294
|
-
"recipients"
|
|
2295
|
-
];
|
|
2296
|
-
const ASSET_FIELDS = ["policyId", "assetName"];
|
|
2297
|
-
const INTEGER_FIELDS = [
|
|
2298
|
-
"slot",
|
|
2299
|
-
"perCallCap",
|
|
2300
|
-
"cap",
|
|
2301
|
-
"lovelacePerCallCap",
|
|
2302
|
-
"lovelaceCap",
|
|
2303
|
-
"expiresAt"
|
|
2304
|
-
];
|
|
2305
|
-
const MAX_ASSET_NAME_BYTES = 32;
|
|
2306
|
-
/** Canonical decimal: no sign, no leading zero, no exponent. */
|
|
2307
|
-
const NON_NEGATIVE_DECIMAL = /^(?:0|[1-9]\d*)$/;
|
|
2308
|
-
const isNonNegativeDecimal = (value) => typeof value === "string" && NON_NEGATIVE_DECIMAL.test(value);
|
|
2309
|
-
const malformed = (reason, field) => require_hex.fail({
|
|
2310
|
-
code: "malformed-requested-scope",
|
|
2311
|
-
reason,
|
|
2312
|
-
...field === void 0 ? {} : { field }
|
|
2313
|
-
});
|
|
2314
|
-
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2315
|
-
const hasExactFields = (value, fields) => {
|
|
2316
|
-
const keys = Object.keys(value);
|
|
2317
|
-
return keys.length === fields.length && fields.every((field) => keys.includes(field));
|
|
2318
|
-
};
|
|
2319
|
-
const isShelleyPaymentAddress = (value) => canonicalRecipientOf(value) !== void 0;
|
|
2320
|
-
const isWellFormedAsset = (asset) => {
|
|
2321
|
-
const { policyId, assetName } = asset;
|
|
2322
|
-
if (policyId === "") return assetName === "";
|
|
2323
|
-
return require_hex.isHash28Hex(policyId) && require_hex.isLowerCaseHex(assetName) && assetName.length <= MAX_ASSET_NAME_BYTES * 2;
|
|
2324
|
-
};
|
|
2325
|
-
/**
|
|
2326
|
-
* Reads a requested scope from protocol JSON, refusing anything but the
|
|
2327
|
-
* exact shape `serializeCustodyRequestedGrantScope` writes: no missing or
|
|
2328
|
-
* extra field, canonical non-negative decimal integers, a positive expiry,
|
|
2329
|
-
* a 28-byte grantee, a lovelace or 28-byte policy asset with a name of at
|
|
2330
|
-
* most 32 bytes, and at most 8 Shelley payment addresses in canonical
|
|
2331
|
-
* bech32. Network membership of the recipients is checked when comparing.
|
|
2332
|
-
*
|
|
2333
|
-
* @param input - the scope as JSON text or as an already parsed value
|
|
2334
|
-
*/
|
|
2335
|
-
const parseCustodyRequestedGrantScope = (input) => {
|
|
2336
|
-
let value = input;
|
|
2337
|
-
if (typeof input === "string") try {
|
|
2338
|
-
value = JSON.parse(input);
|
|
2339
|
-
} catch {
|
|
2340
|
-
return malformed("expected JSON");
|
|
2341
|
-
}
|
|
2342
|
-
if (!isPlainObject(value) || !hasExactFields(value, SCOPE_FIELDS)) return malformed(`expected exactly ${SCOPE_FIELDS.join(", ")}`);
|
|
2343
|
-
const integerField = INTEGER_FIELDS.find((field) => !isNonNegativeDecimal(value[field]));
|
|
2344
|
-
if (integerField) return malformed("expected a non-negative decimal string", integerField);
|
|
2345
|
-
if (value.expiresAt === "0") return malformed("expected a positive expiry", "expiresAt");
|
|
2346
|
-
if (!require_hex.isHash28Hex(value.grantee)) return malformed("expected 28 bytes of lower-case hex", "grantee");
|
|
2347
|
-
const { asset, recipients } = value;
|
|
2348
|
-
if (!isPlainObject(asset) || !hasExactFields(asset, ASSET_FIELDS) || !isWellFormedAsset(asset)) return malformed("expected lovelace or a policy id and asset name", "asset");
|
|
2349
|
-
if (!Array.isArray(recipients) || recipients.length > 8 || !recipients.every(isShelleyPaymentAddress)) return malformed(`expected at most 8 bech32 payment addresses`, "recipients");
|
|
2350
|
-
const integers = value;
|
|
2351
|
-
return require_hex.ok({
|
|
2352
|
-
slot: BigInt(integers.slot),
|
|
2353
|
-
grantee: value.grantee,
|
|
2354
|
-
asset: {
|
|
2355
|
-
policyId: asset.policyId,
|
|
2356
|
-
assetName: asset.assetName
|
|
2357
|
-
},
|
|
2358
|
-
perCallCap: BigInt(integers.perCallCap),
|
|
2359
|
-
cap: BigInt(integers.cap),
|
|
2360
|
-
lovelacePerCallCap: BigInt(integers.lovelacePerCallCap),
|
|
2361
|
-
lovelaceCap: BigInt(integers.lovelaceCap),
|
|
2362
|
-
expiresAt: BigInt(integers.expiresAt),
|
|
2363
|
-
recipients
|
|
2364
|
-
});
|
|
2365
|
-
};
|
|
2366
|
-
//#endregion
|
|
2367
|
-
//#region ../../packages/lib/cardano-custody/src/classify.ts
|
|
2368
|
-
/** The tokens of a value under a policy. */
|
|
2369
|
-
const tokensUnder = (value, policyId) => [...value.assets?.entries() ?? []].filter(([assetId, quantity]) => quantity !== 0n && _laceio_cardano_core.Cardano.AssetId.getPolicyId(assetId) === policyId).map(([assetId]) => assetId);
|
|
2370
|
-
/** Reads a UTxO that holds exactly one grant token of the account, in quantity one. */
|
|
2371
|
-
const classifyGrant = (utxo, token, slot) => {
|
|
2372
|
-
const [, output] = utxo;
|
|
2373
|
-
if (output.value.assets?.size !== 1) return { anomaly: {
|
|
2374
|
-
code: "grant-value-not-strict",
|
|
2375
|
-
utxo,
|
|
2376
|
-
slot
|
|
2377
|
-
} };
|
|
2378
|
-
if (output.datum === void 0) return { anomaly: {
|
|
2379
|
-
code: "grant-datum-not-inline",
|
|
2380
|
-
utxo,
|
|
2381
|
-
slot
|
|
2382
|
-
} };
|
|
2383
|
-
const decoded = require_outputs.decodeCustodyGrant(output.datum);
|
|
2384
|
-
if (!decoded.ok) return { anomaly: {
|
|
2385
|
-
code: "grant-datum-malformed",
|
|
2386
|
-
utxo,
|
|
2387
|
-
slot,
|
|
2388
|
-
error: decoded.error
|
|
2389
|
-
} };
|
|
2390
|
-
if (decoded.value.slot !== slot) return { anomaly: {
|
|
2391
|
-
code: "grant-slot-mismatch",
|
|
2392
|
-
utxo,
|
|
2393
|
-
slot,
|
|
2394
|
-
datumSlot: decoded.value.slot
|
|
2395
|
-
} };
|
|
2396
|
-
return { grant: {
|
|
2397
|
-
utxo,
|
|
2398
|
-
grant: decoded.value,
|
|
2399
|
-
token
|
|
2400
|
-
} };
|
|
2401
|
-
};
|
|
2402
|
-
/** The grant token and its slot, when the tokens are exactly one own grant token held once. */
|
|
2403
|
-
const ownGrantToken = (account, value, tokens) => {
|
|
2404
|
-
const [token] = tokens;
|
|
2405
|
-
if (tokens.length !== 1 || value.assets?.get(token) !== 1n) return void 0;
|
|
2406
|
-
const name = require_tokens.parseCustodyAccountTokenName(_laceio_cardano_core.Cardano.AssetId.getAssetName(token));
|
|
2407
|
-
return name?.kind === "grant" && name.stakeScriptHash === account.stakeScriptHash ? {
|
|
2408
|
-
token,
|
|
2409
|
-
slot: name.slot
|
|
2410
|
-
} : void 0;
|
|
2411
|
-
};
|
|
2412
|
-
/**
|
|
2413
|
-
* Sorts an account's UTxOs by kind. UTxOs not at the exact account address
|
|
2414
|
-
* are ignored. It never throws: a UTxO that holds an account token in a
|
|
2415
|
-
* shape the contract rules out is listed in `anomalies`.
|
|
2416
|
-
*/
|
|
2417
|
-
const classifyCustodyAccountUtxos = (account, utxos) => {
|
|
2418
|
-
const policyId = _laceio_cardano_core.Cardano.PolicyId(account.accountValidatorHash);
|
|
2419
|
-
const stateNft = _laceio_cardano_core.Cardano.AssetId.fromParts(policyId, _laceio_cardano_core.Cardano.AssetName(account.stakeScriptHash));
|
|
2420
|
-
const controls = [];
|
|
2421
|
-
const grants = [];
|
|
2422
|
-
const own = [];
|
|
2423
|
-
const foreign = [];
|
|
2424
|
-
const funds = [];
|
|
2425
|
-
const stranded = [];
|
|
2426
|
-
const anomalies = [];
|
|
2427
|
-
for (const utxo of utxos) {
|
|
2428
|
-
const [, output] = utxo;
|
|
2429
|
-
if (output.address !== account.address) continue;
|
|
2430
|
-
const tokens = tokensUnder(output.value, policyId);
|
|
2431
|
-
if (tokens.includes(stateNft)) controls.push(utxo);
|
|
2432
|
-
else if (tokens.length > 0) {
|
|
2433
|
-
const grantToken = ownGrantToken(account, output.value, tokens);
|
|
2434
|
-
if (!grantToken) {
|
|
2435
|
-
anomalies.push({
|
|
2436
|
-
code: "unexpected-account-tokens",
|
|
2437
|
-
utxo,
|
|
2438
|
-
assetIds: tokens
|
|
2439
|
-
});
|
|
2440
|
-
continue;
|
|
2441
|
-
}
|
|
2442
|
-
const classified = classifyGrant(utxo, grantToken.token, grantToken.slot);
|
|
2443
|
-
if ("grant" in classified) grants.push(classified.grant);
|
|
2444
|
-
else anomalies.push(classified.anomaly);
|
|
2445
|
-
} else if (output.datum !== void 0) (output.scriptReference === void 0 && isCustodyReserveDatum(output.datum) ? own : foreign).push(utxo);
|
|
2446
|
-
else if (output.datumHash === void 0) funds.push(utxo);
|
|
2447
|
-
else stranded.push(utxo);
|
|
2448
|
-
}
|
|
2449
|
-
grants.sort((a, b) => a.grant.slot < b.grant.slot ? -1 : a.grant.slot > b.grant.slot ? 1 : 0);
|
|
2450
|
-
return {
|
|
2451
|
-
controls,
|
|
2452
|
-
grants,
|
|
2453
|
-
reserves: {
|
|
2454
|
-
own,
|
|
2455
|
-
foreign
|
|
2456
|
-
},
|
|
2457
|
-
funds,
|
|
2458
|
-
stranded,
|
|
2459
|
-
anomalies
|
|
2460
|
-
};
|
|
2461
|
-
};
|
|
2462
|
-
/**
|
|
2463
|
-
* The liveness of a grant against a control state at `now`. A grant dead for
|
|
2464
|
-
* several reasons takes the first of superseded, revoked and expired, so
|
|
2465
|
-
* `expired` alone means a sweep needs a validity interval that starts after
|
|
2466
|
-
* the expiry.
|
|
2467
|
-
*
|
|
2468
|
-
* @param now - POSIX milliseconds; a non-finite value counts as past every expiry
|
|
2469
|
-
*/
|
|
2470
|
-
const custodyGrantLiveness = (grant, state, now) => {
|
|
2471
|
-
if (grant.generation < state.grantGeneration) return "superseded";
|
|
2472
|
-
if (grant.generation > state.grantGeneration) return "future-generation";
|
|
2473
|
-
if (!require_outputs.isCustodyGrantCurrent(grant, state)) return "revoked";
|
|
2474
|
-
return require_outputs.isCustodyGrantExpired(grant, now) ? "expired" : "current";
|
|
2475
|
-
};
|
|
2476
|
-
//#endregion
|
|
2477
|
-
//#region ../../packages/lib/cardano-custody/src/device-key.ts
|
|
2478
|
-
/**
|
|
2479
|
-
* The CIP-1854 path `m/1854'/1815'/a'/0/i` of custody device and creator
|
|
2480
|
-
* keys. The account index `a` belongs to the contract version and network:
|
|
2481
|
-
* `custodyDeviceAccountIndex` gives it.
|
|
2482
|
-
*/
|
|
2483
|
-
const CUSTODY_DEVICE_KEY_PATH = {
|
|
2484
|
-
purpose: 1854,
|
|
2485
|
-
coinType: 1815,
|
|
2486
|
-
role: 0
|
|
2487
|
-
};
|
|
2488
|
-
/** First index that is a hardened derivation; device keys use soft ones only. */
|
|
2489
|
-
const HARDENED_INDEX = 2147483648;
|
|
2490
|
-
/**
|
|
2491
|
-
* The device key at `m/1854'/1815'/a'/0/index`, derived from the CIP-1854
|
|
2492
|
-
* account extended public key of account `a` by soft derivation, so no
|
|
2493
|
-
* secret is needed.
|
|
2494
|
-
*/
|
|
2495
|
-
const deriveCustodyDeviceKey = async (accountPublicKey, index) => {
|
|
2496
|
-
if (!Number.isInteger(index) || index < 0 || index >= HARDENED_INDEX) return require_hex.fail({
|
|
2497
|
-
code: "invalid-device-index",
|
|
2498
|
-
index
|
|
2499
|
-
});
|
|
2500
|
-
await _laceio_cardano_crypto.ready();
|
|
2501
|
-
try {
|
|
2502
|
-
const publicKey = _laceio_cardano_crypto.Bip32PublicKey.fromHex(accountPublicKey).derive([CUSTODY_DEVICE_KEY_PATH.role, index]).toRawKey();
|
|
2503
|
-
return require_hex.ok({
|
|
2504
|
-
index,
|
|
2505
|
-
publicKey: publicKey.hex(),
|
|
2506
|
-
keyHash: publicKey.hash().hex()
|
|
2507
|
-
});
|
|
2508
|
-
} catch {
|
|
2509
|
-
return require_hex.fail({ code: "invalid-account-public-key" });
|
|
2510
|
-
}
|
|
2511
|
-
};
|
|
2512
|
-
/**
|
|
2513
|
-
* A short form of a device key hash for people to compare across devices:
|
|
2514
|
-
* its first and last 8 hex characters.
|
|
2515
|
-
*/
|
|
2516
|
-
const custodyDeviceFingerprint = (keyHash) => `${keyHash.slice(0, 8)}…${keyHash.slice(-8)}`;
|
|
2517
|
-
//#endregion
|
|
2518
|
-
//#region ../../packages/contract/cardano-context/src/custody/account-state.ts
|
|
2519
|
-
/** Whether the account's contract version lists the logic in its catalogue. */
|
|
2520
|
-
const isKnownLogic = (accountValidatorHash, logic) => {
|
|
2521
|
-
const version = require_hex.findCustodyContractVersion(accountValidatorHash);
|
|
2522
|
-
return version.ok && require_hex.findCustodyLogic(version.value, logic).ok;
|
|
2523
|
-
};
|
|
2524
|
-
/**
|
|
2525
|
-
* Classifies a custody account's UTxOs by the rules of the custody contract
|
|
2526
|
-
* and reads the control state.
|
|
2527
|
-
*/
|
|
2528
|
-
const classifyCardanoCustodyAccount = ({ account, utxos, withdrawableRewards, protocolParameters }) => {
|
|
2529
|
-
const { controls, grants, reserves, funds, stranded, anomalies } = classifyCustodyAccountUtxos(account, utxos);
|
|
2530
|
-
const holdings = {
|
|
2531
|
-
funds,
|
|
2532
|
-
reserves,
|
|
2533
|
-
grants,
|
|
2534
|
-
grantLockedLovelace: grants.reduce((total, { utxo: [, output] }) => total + output.value.coins, 0n),
|
|
2535
|
-
stranded,
|
|
2536
|
-
anomalies,
|
|
2537
|
-
withdrawableRewards
|
|
2538
|
-
};
|
|
2539
|
-
if (controls.length === 0) return {
|
|
2540
|
-
...holdings,
|
|
2541
|
-
status: "notCreated"
|
|
2542
|
-
};
|
|
2543
|
-
const invalid = (reason) => ({
|
|
2544
|
-
...holdings,
|
|
2545
|
-
status: "invalid",
|
|
2546
|
-
reason,
|
|
2547
|
-
controlUtxos: controls
|
|
2548
|
-
});
|
|
2549
|
-
if (controls.length > 1) return invalid({ code: "multiple-control-utxos" });
|
|
2550
|
-
const [controlUtxo] = controls;
|
|
2551
|
-
const [, controlOutput] = controlUtxo;
|
|
2552
|
-
if (controlOutput.datum === void 0) return invalid({ code: "missing-inline-datum" });
|
|
2553
|
-
const logic = require_outputs.decodeCustodyStateLogic(controlOutput.datum);
|
|
2554
|
-
if (!logic.ok) return invalid({
|
|
2555
|
-
code: "undecodable-state",
|
|
2556
|
-
error: logic.error
|
|
2557
|
-
});
|
|
2558
|
-
if (!isKnownLogic(account.accountValidatorHash, logic.value)) return invalid({
|
|
2559
|
-
code: "unknown-logic",
|
|
2560
|
-
logic: logic.value
|
|
2561
|
-
});
|
|
2562
|
-
const decoded = require_outputs.decodeCustodyAccountState(controlOutput.datum);
|
|
2563
|
-
if (!decoded.ok) return invalid({
|
|
2564
|
-
code: "undecodable-state",
|
|
2565
|
-
error: decoded.error
|
|
2566
|
-
});
|
|
2567
|
-
const controlMinimumLovelace = (0, _laceio_cardano_tx_construction.minAdaRequired)(controlOutput, BigInt(protocolParameters.coinsPerUtxoByte));
|
|
2568
|
-
const controlLovelace = controlOutput.value.coins;
|
|
2569
|
-
const deviceKeyHash = account.device?.keyHash;
|
|
2570
|
-
return {
|
|
2571
|
-
...holdings,
|
|
2572
|
-
status: "live",
|
|
2573
|
-
controlUtxo,
|
|
2574
|
-
state: decoded.value,
|
|
2575
|
-
controlMinimumLovelace,
|
|
2576
|
-
controlHeadroomLovelace: controlLovelace > controlMinimumLovelace ? controlLovelace - controlMinimumLovelace : 0n,
|
|
2577
|
-
...deviceKeyHash !== void 0 && { currentDeviceListed: decoded.value.devices.some((device) => device.toLowerCase() === deviceKeyHash.toLowerCase()) }
|
|
2578
|
-
};
|
|
2579
|
-
};
|
|
2580
|
-
//#endregion
|
|
2581
|
-
//#region ../../packages/contract/cardano-context/src/custody/build-transaction.ts
|
|
2582
|
-
/**
|
|
2583
|
-
* The most lovelace a grant spend's fee may take when the operation names
|
|
2584
|
-
* no `feeBound`: 1,500,000 lovelace. The spend lowers the grant's remaining caps by its outputs
|
|
2585
|
-
* and fee before the fee is final, so the bound is what the caps must leave
|
|
2586
|
-
* room for.
|
|
2587
|
-
*/
|
|
2588
|
-
const CARDANO_CUSTODY_DEFAULT_GRANT_FEE_BOUND = 1500000n;
|
|
2589
|
-
//#endregion
|
|
2590
|
-
//#region ../../packages/contract/cardano-context/src/custody/grant-status.ts
|
|
2591
|
-
/**
|
|
2592
|
-
* Any key hash: an unrestricted grant pays out to any address, and an
|
|
2593
|
-
* enterprise key address makes the smallest output.
|
|
2594
|
-
*/
|
|
2595
|
-
const ANY_KEY_HASH = (0, _laceio_cardano_crypto.Hash28ByteBase16)("00".repeat(28));
|
|
2596
|
-
/** The addresses a grant may pay out to, or one standing for any address. */
|
|
2597
|
-
const payoutAddressesOf = ({ recipients }, networkId) => {
|
|
2598
|
-
if (recipients.length === 0) return [_laceio_cardano_core.Cardano.EnterpriseAddress.fromCredentials(networkId, {
|
|
2599
|
-
type: _laceio_cardano_core.Cardano.CredentialType.KeyHash,
|
|
2600
|
-
hash: ANY_KEY_HASH
|
|
2601
|
-
}).toAddress().toBech32()];
|
|
2602
|
-
return recipients.flatMap((recipient) => {
|
|
2603
|
-
const address = require_outputs.custodyRecipientToAddress(recipient, networkId);
|
|
2604
|
-
return address.ok ? [address.value.toBech32()] : [];
|
|
2605
|
-
});
|
|
2606
|
-
};
|
|
2607
|
-
/** The scope's token, or undefined when its policy id or name is malformed. */
|
|
2608
|
-
const tokenOf = ({ asset }) => {
|
|
2609
|
-
try {
|
|
2610
|
-
return _laceio_cardano_core.Cardano.AssetId.fromParts(_laceio_cardano_core.Cardano.PolicyId(asset.policyId), _laceio_cardano_core.Cardano.AssetName(asset.assetName));
|
|
2611
|
-
} catch {
|
|
2612
|
-
return;
|
|
2613
|
-
}
|
|
2614
|
-
};
|
|
2615
|
-
const smallerOf = (a, b) => a < b ? a : b;
|
|
2616
|
-
/**
|
|
2617
|
-
* Whether a grant scope can no longer pay the smallest spend. That spend
|
|
2618
|
-
* is one output to the cheapest address the grant allows, holding the
|
|
2619
|
-
* ledger minimum lovelace, plus the fee bound. A lovelace grant draws both
|
|
2620
|
-
* from its lovelace caps (`cap`, `perCallCap`). A token grant draws one
|
|
2621
|
-
* token from its token caps and the lovelace from `lovelaceCap` and
|
|
2622
|
-
* `lovelacePerCallCap`. A grant with no address it can pay is exhausted.
|
|
2623
|
-
*/
|
|
2624
|
-
const isCardanoCustodyGrantExhausted = (scope, { protocolParameters, networkId, feeBound = CARDANO_CUSTODY_DEFAULT_GRANT_FEE_BOUND }) => {
|
|
2625
|
-
const isLovelace = require_outputs.isCustodyLovelaceAsset(scope.asset);
|
|
2626
|
-
const token = isLovelace ? void 0 : tokenOf(scope);
|
|
2627
|
-
if (!isLovelace && !token) return true;
|
|
2628
|
-
const addresses = payoutAddressesOf(scope, networkId);
|
|
2629
|
-
if (addresses.length === 0) return true;
|
|
2630
|
-
const coinsPerUtxoByte = BigInt(protocolParameters.coinsPerUtxoByte);
|
|
2631
|
-
const smallestLovelaceSpend = addresses.map((address) => (0, _laceio_cardano_tx_construction.minAdaRequired)({
|
|
2632
|
-
address,
|
|
2633
|
-
value: {
|
|
2634
|
-
coins: 0n,
|
|
2635
|
-
...token && { assets: new Map([[token, 1n]]) }
|
|
2636
|
-
}
|
|
2637
|
-
}, coinsPerUtxoByte)).reduce(smallerOf) + feeBound;
|
|
2638
|
-
if (isLovelace) return smallerOf(scope.cap, scope.perCallCap) < smallestLovelaceSpend;
|
|
2639
|
-
return smallerOf(scope.cap, scope.perCallCap) < 1n || smallerOf(scope.lovelaceCap, scope.lovelacePerCallCap) < smallestLovelaceSpend;
|
|
2640
|
-
};
|
|
2641
|
-
const bySlot = ({ grant: a }, { grant: b }) => a.slot < b.slot ? -1 : a.slot > b.slot ? 1 : 0;
|
|
2642
|
-
/**
|
|
2643
|
-
* The status of a grant the confirmed view holds and the in-flight view
|
|
2644
|
-
* keeps. `grant` is its in-flight version: an unsettled grant spend lowers
|
|
2645
|
-
* its caps and changes nothing else.
|
|
2646
|
-
*/
|
|
2647
|
-
const confirmedGrantStatus = (grant, { confirmed, inFlight, now, ...pricing }) => {
|
|
2648
|
-
const liveness = custodyGrantLiveness(grant, confirmed, now);
|
|
2649
|
-
if (liveness === "revoked" || liveness === "superseded") return liveness;
|
|
2650
|
-
if (liveness === "future-generation") return "pending";
|
|
2651
|
-
const next = custodyGrantLiveness(grant, inFlight, now);
|
|
2652
|
-
if (next === "revoked" || next === "superseded") return "revoking";
|
|
2653
|
-
if (liveness === "expired") return "expired";
|
|
2654
|
-
return isCardanoCustodyGrantExhausted(grant.scope, pricing) ? "exhausted" : "live";
|
|
2655
|
-
};
|
|
2656
|
-
/**
|
|
2657
|
-
* The status of every grant UTxO of a custody account, in slot order.
|
|
2658
|
-
* `inFlight` is the account after the submitted operations that have not
|
|
2659
|
-
* settled; `confirmed` is the account on chain, absent while it has no
|
|
2660
|
-
* confirmed control state. A grant is known by its slot, which no grant
|
|
2661
|
-
* ever reuses. `now` is POSIX milliseconds: the time of the chain tip.
|
|
2662
|
-
*
|
|
2663
|
-
* A grant only the in-flight view holds is `pending`, and one only the
|
|
2664
|
-
* confirmed view holds is `sweeping`. A grant both hold is judged against
|
|
2665
|
-
* the confirmed state, and is `revoking` when the in-flight state kills it.
|
|
2666
|
-
* Its listed version is the in-flight one, so an unsettled grant spend
|
|
2667
|
-
* shows its lowered caps at once.
|
|
2668
|
-
*/
|
|
2669
|
-
const classifyCardanoCustodyGrants = ({ confirmed, inFlight, now, protocolParameters, networkId }) => {
|
|
2670
|
-
const confirmedSlots = new Set(confirmed?.grants.map(({ grant }) => grant.slot));
|
|
2671
|
-
const inFlightSlots = new Set(inFlight.grants.map(({ grant }) => grant.slot));
|
|
2672
|
-
const kept = inFlight.grants.map((grantUtxo) => ({
|
|
2673
|
-
...grantUtxo,
|
|
2674
|
-
status: confirmed && confirmedSlots.has(grantUtxo.grant.slot) ? confirmedGrantStatus(grantUtxo.grant, {
|
|
2675
|
-
confirmed: confirmed.state,
|
|
2676
|
-
inFlight: inFlight.state,
|
|
2677
|
-
now,
|
|
2678
|
-
protocolParameters,
|
|
2679
|
-
networkId
|
|
2680
|
-
}) : "pending"
|
|
2681
|
-
}));
|
|
2682
|
-
const swept = (confirmed?.grants ?? []).filter(({ grant }) => !inFlightSlots.has(grant.slot)).map((grantUtxo) => ({
|
|
2683
|
-
...grantUtxo,
|
|
2684
|
-
status: "sweeping"
|
|
2685
|
-
}));
|
|
2686
|
-
return [...kept, ...swept].sort(bySlot);
|
|
2687
|
-
};
|
|
2688
|
-
//#endregion
|
|
2689
|
-
Object.defineProperty(exports, "CARDANO_CUSTODY_DEFAULT_GRANT_FEE_BOUND", {
|
|
2690
|
-
enumerable: true,
|
|
2691
|
-
get: function() {
|
|
2692
|
-
return CARDANO_CUSTODY_DEFAULT_GRANT_FEE_BOUND;
|
|
2693
|
-
}
|
|
2694
|
-
});
|
|
2695
|
-
Object.defineProperty(exports, "CUSTODY_ACCOUNT_RECORD_TYPE", {
|
|
2696
|
-
enumerable: true,
|
|
2697
|
-
get: function() {
|
|
2698
|
-
return CUSTODY_ACCOUNT_RECORD_TYPE;
|
|
2699
|
-
}
|
|
2700
|
-
});
|
|
2701
|
-
Object.defineProperty(exports, "canonicalCustodyAccountRecord", {
|
|
2702
|
-
enumerable: true,
|
|
2703
|
-
get: function() {
|
|
2704
|
-
return canonicalCustodyAccountRecord;
|
|
2705
|
-
}
|
|
2706
|
-
});
|
|
2707
|
-
Object.defineProperty(exports, "classifyCardanoCustodyAccount", {
|
|
2708
|
-
enumerable: true,
|
|
2709
|
-
get: function() {
|
|
2710
|
-
return classifyCardanoCustodyAccount;
|
|
2711
|
-
}
|
|
2712
|
-
});
|
|
2713
|
-
Object.defineProperty(exports, "classifyCardanoCustodyGrants", {
|
|
2714
|
-
enumerable: true,
|
|
2715
|
-
get: function() {
|
|
2716
|
-
return classifyCardanoCustodyGrants;
|
|
2717
|
-
}
|
|
2718
|
-
});
|
|
2719
|
-
Object.defineProperty(exports, "classifyCustodyAccountUtxos", {
|
|
2720
|
-
enumerable: true,
|
|
2721
|
-
get: function() {
|
|
2722
|
-
return classifyCustodyAccountUtxos;
|
|
2723
|
-
}
|
|
2724
|
-
});
|
|
2725
|
-
Object.defineProperty(exports, "compareRequestedGrantScope", {
|
|
2726
|
-
enumerable: true,
|
|
2727
|
-
get: function() {
|
|
2728
|
-
return compareRequestedGrantScope;
|
|
2729
|
-
}
|
|
2730
|
-
});
|
|
2731
|
-
Object.defineProperty(exports, "createCustodyAccountRecord", {
|
|
2732
|
-
enumerable: true,
|
|
2733
|
-
get: function() {
|
|
2734
|
-
return createCustodyAccountRecord;
|
|
2735
|
-
}
|
|
2736
|
-
});
|
|
2737
|
-
Object.defineProperty(exports, "custodyDeviceFingerprint", {
|
|
2738
|
-
enumerable: true,
|
|
2739
|
-
get: function() {
|
|
2740
|
-
return custodyDeviceFingerprint;
|
|
2741
|
-
}
|
|
2742
|
-
});
|
|
2743
|
-
Object.defineProperty(exports, "custodyGrantAfterSpend", {
|
|
2744
|
-
enumerable: true,
|
|
2745
|
-
get: function() {
|
|
2746
|
-
return custodyGrantAfterSpend;
|
|
2747
|
-
}
|
|
2748
|
-
});
|
|
2749
|
-
Object.defineProperty(exports, "custodyGrantLiveness", {
|
|
2750
|
-
enumerable: true,
|
|
2751
|
-
get: function() {
|
|
2752
|
-
return custodyGrantLiveness;
|
|
2753
|
-
}
|
|
2754
|
-
});
|
|
2755
|
-
Object.defineProperty(exports, "deriveCustodyDeviceKey", {
|
|
2756
|
-
enumerable: true,
|
|
2757
|
-
get: function() {
|
|
2758
|
-
return deriveCustodyDeviceKey;
|
|
2759
|
-
}
|
|
2760
|
-
});
|
|
2761
|
-
Object.defineProperty(exports, "encodeCustodyAccountRedeemer", {
|
|
2762
|
-
enumerable: true,
|
|
2763
|
-
get: function() {
|
|
2764
|
-
return encodeCustodyAccountRedeemer;
|
|
2765
|
-
}
|
|
2766
|
-
});
|
|
2767
|
-
Object.defineProperty(exports, "encodeCustodyLogicRedeemer", {
|
|
2768
|
-
enumerable: true,
|
|
2769
|
-
get: function() {
|
|
2770
|
-
return encodeCustodyLogicRedeemer;
|
|
2771
|
-
}
|
|
2772
|
-
});
|
|
2773
|
-
Object.defineProperty(exports, "encodeCustodyMintRedeemer", {
|
|
2774
|
-
enumerable: true,
|
|
2775
|
-
get: function() {
|
|
2776
|
-
return encodeCustodyMintRedeemer;
|
|
2777
|
-
}
|
|
2778
|
-
});
|
|
2779
|
-
Object.defineProperty(exports, "encodeCustodyStakeRedeemer", {
|
|
2780
|
-
enumerable: true,
|
|
2781
|
-
get: function() {
|
|
2782
|
-
return encodeCustodyStakeRedeemer;
|
|
2783
|
-
}
|
|
2784
|
-
});
|
|
2785
|
-
Object.defineProperty(exports, "findCustodyScopeViolations", {
|
|
2786
|
-
enumerable: true,
|
|
2787
|
-
get: function() {
|
|
2788
|
-
return findCustodyScopeViolations;
|
|
2789
|
-
}
|
|
2790
|
-
});
|
|
2791
|
-
Object.defineProperty(exports, "inspectCustodyTransaction", {
|
|
2792
|
-
enumerable: true,
|
|
2793
|
-
get: function() {
|
|
2794
|
-
return inspectCustodyTransaction;
|
|
2795
|
-
}
|
|
2796
|
-
});
|
|
2797
|
-
Object.defineProperty(exports, "isCardanoCustodyGrantExhausted", {
|
|
2798
|
-
enumerable: true,
|
|
2799
|
-
get: function() {
|
|
2800
|
-
return isCardanoCustodyGrantExhausted;
|
|
2801
|
-
}
|
|
2802
|
-
});
|
|
2803
|
-
Object.defineProperty(exports, "parseCustodyAccountRecord", {
|
|
2804
|
-
enumerable: true,
|
|
2805
|
-
get: function() {
|
|
2806
|
-
return parseCustodyAccountRecord;
|
|
2807
|
-
}
|
|
2808
|
-
});
|
|
2809
|
-
Object.defineProperty(exports, "parseCustodyRequestedGrantScope", {
|
|
2810
|
-
enumerable: true,
|
|
2811
|
-
get: function() {
|
|
2812
|
-
return parseCustodyRequestedGrantScope;
|
|
2813
|
-
}
|
|
2814
|
-
});
|
|
2815
|
-
Object.defineProperty(exports, "serializeCustodyRequestedGrantScope", {
|
|
2816
|
-
enumerable: true,
|
|
2817
|
-
get: function() {
|
|
2818
|
-
return serializeCustodyRequestedGrantScope;
|
|
2819
|
-
}
|
|
2820
|
-
});
|