solace-zar-trustless-escrow 0.1.0

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.
Files changed (27) hide show
  1. checksums.yaml +7 -0
  2. data/lib/solace/zar_trustless_escrow/codecs_extensions.rb +101 -0
  3. data/lib/solace/zar_trustless_escrow/composers/claim_composer.rb +136 -0
  4. data/lib/solace/zar_trustless_escrow/composers/deposit_composer.rb +145 -0
  5. data/lib/solace/zar_trustless_escrow/composers/mediated_deposit_composer.rb +173 -0
  6. data/lib/solace/zar_trustless_escrow/composers/mediated_reclaim_composer.rb +137 -0
  7. data/lib/solace/zar_trustless_escrow/composers/mediated_release_composer.rb +157 -0
  8. data/lib/solace/zar_trustless_escrow/composers/reclaim_composer.rb +135 -0
  9. data/lib/solace/zar_trustless_escrow/composers/token_account_init_composer.rb +88 -0
  10. data/lib/solace/zar_trustless_escrow/constants.rb +47 -0
  11. data/lib/solace/zar_trustless_escrow/errors/program_error.rb +30 -0
  12. data/lib/solace/zar_trustless_escrow/errors.rb +104 -0
  13. data/lib/solace/zar_trustless_escrow/instructions/claim_instruction.rb +60 -0
  14. data/lib/solace/zar_trustless_escrow/instructions/deposit_instruction.rb +65 -0
  15. data/lib/solace/zar_trustless_escrow/instructions/mediated_deposit_instruction.rb +74 -0
  16. data/lib/solace/zar_trustless_escrow/instructions/mediated_reclaim_instruction.rb +60 -0
  17. data/lib/solace/zar_trustless_escrow/instructions/mediated_release_instruction.rb +65 -0
  18. data/lib/solace/zar_trustless_escrow/instructions/reclaim_instruction.rb +60 -0
  19. data/lib/solace/zar_trustless_escrow/instructions/token_account_init_instruction.rb +48 -0
  20. data/lib/solace/zar_trustless_escrow/programs/zar_trustless_escrow/escrow_deposit_operations.rb +243 -0
  21. data/lib/solace/zar_trustless_escrow/programs/zar_trustless_escrow/mediated_escrow_deposit_operations.rb +259 -0
  22. data/lib/solace/zar_trustless_escrow/programs/zar_trustless_escrow.rb +148 -0
  23. data/lib/solace/zar_trustless_escrow/types/escrow_deposit.rb +44 -0
  24. data/lib/solace/zar_trustless_escrow/types/mediated_escrow_deposit.rb +50 -0
  25. data/lib/solace/zar_trustless_escrow/version.rb +7 -0
  26. data/lib/solace/zar_trustless_escrow.rb +38 -0
  27. metadata +171 -0
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module Composers
5
+ # Composes a `mediated_release` instruction for the ZAR Trustless Escrow
6
+ # program.
7
+ #
8
+ # The mediator releases a MediatedEscrowDeposit to the recipient (the depositor
9
+ # or the beneficiary) and closes the deposit, sending rent to the rent
10
+ # collector (which defaults to the mediator when none was set at deposit time).
11
+ #
12
+ # Required params:
13
+ # :mint [#to_s] Mint of the escrowed tokens.
14
+ # :mediator [#to_s, Keypair] Mediator (readonly signer).
15
+ # :recipient [#to_s] Depositor or beneficiary receiving tokens.
16
+ # :recipient_token_account [#to_s] Recipient's destination token account (ATA).
17
+ # :mediated_escrow_deposit [#to_s] MediatedEscrowDeposit PDA.
18
+ # :program_token_account [#to_s] Per-mint program vault PDA.
19
+ # :rent_collector [#to_s] Account that receives the closed-account rent.
20
+ # :fee_payer [#to_s, Keypair] Fee payer (writable signer).
21
+ # :id [#to_s] Unique id used to derive the escrow PDA.
22
+ #
23
+ # Optional params:
24
+ # :program_id [#to_s] Escrow program id (default: PROGRAM_ID, mainnet).
25
+ # :token_program_id [#to_s] Token program (default: legacy SPL Token).
26
+ class ZarTrustlessEscrowMediatedReleaseComposer < Base
27
+ # Extracts the mint address from the params
28
+ #
29
+ # @return [String] The mint address
30
+ def mint
31
+ params[:mint].to_s
32
+ end
33
+
34
+ # Extracts the mediator from the params
35
+ #
36
+ # @return [String] The mediator address
37
+ def mediator
38
+ params[:mediator].to_s
39
+ end
40
+
41
+ # Extracts the recipient from the params
42
+ #
43
+ # @return [String] The recipient address
44
+ def recipient
45
+ params[:recipient].to_s
46
+ end
47
+
48
+ # Extracts the recipient's destination token account from the params
49
+ #
50
+ # @return [String] The recipient token account address
51
+ def recipient_token_account
52
+ params[:recipient_token_account].to_s
53
+ end
54
+
55
+ # Extracts the MediatedEscrowDeposit PDA address from the params
56
+ #
57
+ # @return [String] The mediated escrow deposit address
58
+ def mediated_escrow_deposit
59
+ params[:mediated_escrow_deposit].to_s
60
+ end
61
+
62
+ # Extracts the per-mint program vault address from the params
63
+ #
64
+ # @return [String] The program token account address
65
+ def program_token_account
66
+ params[:program_token_account].to_s
67
+ end
68
+
69
+ # Extracts the rent collector from the params
70
+ #
71
+ # @return [String] The rent collector address
72
+ def rent_collector
73
+ params[:rent_collector].to_s
74
+ end
75
+
76
+ # Extracts the fee payer address from the params
77
+ #
78
+ # @return [String] The fee payer address
79
+ def fee_payer
80
+ params[:fee_payer].to_s
81
+ end
82
+
83
+ # Extracts the unique escrow id from the params
84
+ #
85
+ # @return [String] The escrow id
86
+ def id
87
+ params[:id].to_s
88
+ end
89
+
90
+ # Returns the escrow program id (defaults to the mainnet PROGRAM_ID)
91
+ #
92
+ # @return [String] The escrow program id
93
+ def program_id
94
+ (params[:program_id] || Solace::ZarTrustlessEscrow::PROGRAM_ID).to_s
95
+ end
96
+
97
+ # Returns the token program id (defaults to the legacy SPL Token program)
98
+ #
99
+ # @return [String] The token program id
100
+ def token_program_id
101
+ (params[:token_program_id] || Solace::Constants::TOKEN_PROGRAM_ID).to_s
102
+ end
103
+
104
+ # Returns the System Program id
105
+ #
106
+ # @return [String] The System Program id
107
+ def system_program
108
+ Solace::Constants::SYSTEM_PROGRAM_ID
109
+ end
110
+
111
+ # Returns the Associated Token Account Program id
112
+ #
113
+ # @return [String] The Associated Token Account Program id
114
+ def associated_token_program
115
+ Solace::Constants::ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID
116
+ end
117
+
118
+ # Declares all accounts required by this instruction.
119
+ def setup_accounts
120
+ account_context.add_readonly_nonsigner(mint)
121
+ account_context.add_readonly_signer(mediator)
122
+ account_context.add_readonly_nonsigner(recipient)
123
+ account_context.add_writable_nonsigner(recipient_token_account)
124
+ account_context.add_writable_nonsigner(mediated_escrow_deposit)
125
+ account_context.add_writable_nonsigner(program_token_account)
126
+ account_context.add_writable_nonsigner(rent_collector)
127
+ account_context.add_writable_signer(fee_payer)
128
+ account_context.add_readonly_nonsigner(system_program)
129
+ account_context.add_readonly_nonsigner(token_program_id)
130
+ account_context.add_readonly_nonsigner(associated_token_program)
131
+ account_context.add_readonly_nonsigner(program_id)
132
+ end
133
+
134
+ # Builds the instruction with resolved account indices.
135
+ #
136
+ # @param context [Solace::Utils::AccountContext] Merged context from TransactionComposer.
137
+ # @return [Solace::Instruction]
138
+ def build_instruction(context)
139
+ Solace::ZarTrustlessEscrow::Instructions::MediatedReleaseInstruction.build(
140
+ id:,
141
+ mint_index: context.index_of(mint),
142
+ mediator_index: context.index_of(mediator),
143
+ recipient_index: context.index_of(recipient),
144
+ recipient_token_account_index: context.index_of(recipient_token_account),
145
+ mediated_escrow_deposit_index: context.index_of(mediated_escrow_deposit),
146
+ program_token_account_index: context.index_of(program_token_account),
147
+ rent_collector_index: context.index_of(rent_collector),
148
+ fee_payer_index: context.index_of(fee_payer),
149
+ system_program_index: context.index_of(system_program),
150
+ token_program_index: context.index_of(token_program_id),
151
+ associated_token_program_index: context.index_of(associated_token_program),
152
+ program_index: context.index_of(program_id)
153
+ )
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module Composers
5
+ # Composes a `re_claim` instruction for the ZAR Trustless Escrow program.
6
+ #
7
+ # The original depositor (or sponsor, as fee payer) reclaims an unclaimed
8
+ # EscrowDeposit and closes the deposit account.
9
+ #
10
+ # Required params:
11
+ # :mint [#to_s] Mint of the escrowed tokens.
12
+ # :depositor [#to_s] Original depositor (receives the tokens).
13
+ # :depositor_token_account [#to_s] Depositor's destination token account (ATA).
14
+ # :escrow_deposit [#to_s] EscrowDeposit PDA.
15
+ # :program_token_account [#to_s] Per-mint program vault PDA.
16
+ # :fee_payer [#to_s, Keypair] Fee payer (writable signer).
17
+ # :claim_authority [#to_s] Authority used to derive the escrow PDA.
18
+ #
19
+ # Optional params:
20
+ # :program_id [#to_s] Escrow program id (default: PROGRAM_ID, mainnet).
21
+ # :token_program_id [#to_s] Token program (default: legacy SPL Token).
22
+ class ZarTrustlessEscrowReclaimComposer < Base
23
+ # Extracts the mint address from the params
24
+ #
25
+ # @return [String] The mint address
26
+ def mint
27
+ params[:mint].to_s
28
+ end
29
+
30
+ # Extracts the depositor address from the params
31
+ #
32
+ # @return [String] The depositor address
33
+ def depositor
34
+ params[:depositor].to_s
35
+ end
36
+
37
+ # Extracts the depositor's destination token account from the params
38
+ #
39
+ # @return [String] The depositor token account address
40
+ def depositor_token_account
41
+ params[:depositor_token_account].to_s
42
+ end
43
+
44
+ # Extracts the EscrowDeposit PDA address from the params
45
+ #
46
+ # @return [String] The escrow deposit address
47
+ def escrow_deposit
48
+ params[:escrow_deposit].to_s
49
+ end
50
+
51
+ # Extracts the per-mint program vault address from the params
52
+ #
53
+ # @return [String] The program token account address
54
+ def program_token_account
55
+ params[:program_token_account].to_s
56
+ end
57
+
58
+ # Extracts the fee payer address from the params
59
+ #
60
+ # @return [String] The fee payer address
61
+ def fee_payer
62
+ params[:fee_payer].to_s
63
+ end
64
+
65
+ # Extracts the claim authority from the params
66
+ #
67
+ # @return [String] The claim authority address
68
+ def claim_authority
69
+ params[:claim_authority].to_s
70
+ end
71
+
72
+ # Returns the escrow program id (defaults to the mainnet PROGRAM_ID)
73
+ #
74
+ # @return [String] The escrow program id
75
+ def program_id
76
+ (params[:program_id] || Solace::ZarTrustlessEscrow::PROGRAM_ID).to_s
77
+ end
78
+
79
+ # Returns the token program id (defaults to the legacy SPL Token program)
80
+ #
81
+ # @return [String] The token program id
82
+ def token_program_id
83
+ (params[:token_program_id] || Solace::Constants::TOKEN_PROGRAM_ID).to_s
84
+ end
85
+
86
+ # Returns the System Program id
87
+ #
88
+ # @return [String] The System Program id
89
+ def system_program
90
+ Solace::Constants::SYSTEM_PROGRAM_ID
91
+ end
92
+
93
+ # Returns the Associated Token Account Program id
94
+ #
95
+ # @return [String] The Associated Token Account Program id
96
+ def associated_token_program
97
+ Solace::Constants::ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID
98
+ end
99
+
100
+ # Declares all accounts required by this instruction.
101
+ def setup_accounts
102
+ account_context.add_readonly_nonsigner(mint)
103
+ account_context.add_readonly_nonsigner(depositor)
104
+ account_context.add_writable_nonsigner(depositor_token_account)
105
+ account_context.add_writable_nonsigner(escrow_deposit)
106
+ account_context.add_writable_nonsigner(program_token_account)
107
+ account_context.add_writable_signer(fee_payer)
108
+ account_context.add_readonly_nonsigner(system_program)
109
+ account_context.add_readonly_nonsigner(token_program_id)
110
+ account_context.add_readonly_nonsigner(associated_token_program)
111
+ account_context.add_readonly_nonsigner(program_id)
112
+ end
113
+
114
+ # Builds the instruction with resolved account indices.
115
+ #
116
+ # @param context [Solace::Utils::AccountContext] Merged context from TransactionComposer.
117
+ # @return [Solace::Instruction]
118
+ def build_instruction(context)
119
+ Solace::ZarTrustlessEscrow::Instructions::ReclaimInstruction.build(
120
+ claim_authority:,
121
+ mint_index: context.index_of(mint),
122
+ depositor_index: context.index_of(depositor),
123
+ depositor_token_account_index: context.index_of(depositor_token_account),
124
+ escrow_deposit_index: context.index_of(escrow_deposit),
125
+ program_token_account_index: context.index_of(program_token_account),
126
+ fee_payer_index: context.index_of(fee_payer),
127
+ system_program_index: context.index_of(system_program),
128
+ token_program_index: context.index_of(token_program_id),
129
+ associated_token_program_index: context.index_of(associated_token_program),
130
+ program_index: context.index_of(program_id)
131
+ )
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module Composers
5
+ # Composes a `token_account_init` instruction for the ZAR Trustless Escrow
6
+ # program.
7
+ #
8
+ # Initializes the per-mint program vault token account if it does not already
9
+ # exist. A single vault is shared across all deposits for a given mint.
10
+ #
11
+ # Required params:
12
+ # :mint [#to_s] Mint the vault holds.
13
+ # :program_token_account [#to_s] Per-mint program vault PDA.
14
+ # :fee_payer [#to_s, Keypair] Fee payer (writable signer).
15
+ #
16
+ # Optional params:
17
+ # :program_id [#to_s] Escrow program id (default: PROGRAM_ID, mainnet).
18
+ # :token_program_id [#to_s] Token program (default: legacy SPL Token).
19
+ class ZarTrustlessEscrowTokenAccountInitComposer < Base
20
+ # Extracts the mint address from the params
21
+ #
22
+ # @return [String] The mint address
23
+ def mint
24
+ params[:mint].to_s
25
+ end
26
+
27
+ # Extracts the per-mint program vault address from the params
28
+ #
29
+ # @return [String] The program token account address
30
+ def program_token_account
31
+ params[:program_token_account].to_s
32
+ end
33
+
34
+ # Extracts the fee payer address from the params
35
+ #
36
+ # @return [String] The fee payer address
37
+ def fee_payer
38
+ params[:fee_payer].to_s
39
+ end
40
+
41
+ # Returns the escrow program id (defaults to the mainnet PROGRAM_ID)
42
+ #
43
+ # @return [String] The escrow program id
44
+ def program_id
45
+ (params[:program_id] || Solace::ZarTrustlessEscrow::PROGRAM_ID).to_s
46
+ end
47
+
48
+ # Returns the token program id (defaults to the legacy SPL Token program)
49
+ #
50
+ # @return [String] The token program id
51
+ def token_program_id
52
+ (params[:token_program_id] || Solace::Constants::TOKEN_PROGRAM_ID).to_s
53
+ end
54
+
55
+ # Returns the System Program id
56
+ #
57
+ # @return [String] The System Program id
58
+ def system_program
59
+ Solace::Constants::SYSTEM_PROGRAM_ID
60
+ end
61
+
62
+ # Declares all accounts required by this instruction.
63
+ def setup_accounts
64
+ account_context.add_writable_nonsigner(mint)
65
+ account_context.add_writable_nonsigner(program_token_account)
66
+ account_context.add_writable_signer(fee_payer)
67
+ account_context.add_readonly_nonsigner(system_program)
68
+ account_context.add_readonly_nonsigner(token_program_id)
69
+ account_context.add_readonly_nonsigner(program_id)
70
+ end
71
+
72
+ # Builds the instruction with resolved account indices.
73
+ #
74
+ # @param context [Solace::Utils::AccountContext] Merged context from TransactionComposer.
75
+ # @return [Solace::Instruction]
76
+ def build_instruction(context)
77
+ Solace::ZarTrustlessEscrow::Instructions::TokenAccountInitInstruction.build(
78
+ mint_index: context.index_of(mint),
79
+ program_token_account_index: context.index_of(program_token_account),
80
+ fee_payer_index: context.index_of(fee_payer),
81
+ system_program_index: context.index_of(system_program),
82
+ token_program_index: context.index_of(token_program_id),
83
+ program_index: context.index_of(program_id)
84
+ )
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ # Program IDs, PDA seeds, and cluster helpers for the ZAR Trustless Escrow
5
+ # program. The values mirror the [programs.*] entries in Anchor.toml and the
6
+ # constants exported by the TypeScript SDK.
7
+ module ZarTrustlessEscrow
8
+ # Per-cluster on-chain program IDs for the ZAR Trustless Escrow program.
9
+ # These mirror the [programs.*] entries in Anchor.toml and the PROGRAM_ID
10
+ # constants exported by the TypeScript SDK.
11
+ MAINNET_PROGRAM_ID = 'ZARxRie8kHmJ3V3GSRFS6CUDjCMm5pEoZyhEddq6FSu'
12
+ DEVNET_PROGRAM_ID = 'ZARTiAgmDYUcqbx35KF4cfJYudyQAeM4iagCXs5AR9V'
13
+ TESTNET_PROGRAM_ID = 'ZARJV7Y9nLeJYPFdcd9xdXBxGWN84jDR1TdiiyZhKSo'
14
+ LOCALNET_PROGRAM_ID = 'HKzzmBQk3uat5Vak8RyPyDe3f6Fi9boR1vDV6qMrSXmH'
15
+
16
+ # Default program ID used when a cluster is not specified. Defaults to mainnet.
17
+ PROGRAM_ID = MAINNET_PROGRAM_ID
18
+
19
+ # PDA seed prefix for an EscrowDeposit account, derived from
20
+ # ["escrow_deposit", claim_authority].
21
+ ESCROW_DEPOSIT_SEED = 'escrow_deposit'
22
+
23
+ # PDA seed prefix for a MediatedEscrowDeposit account, derived from
24
+ # ["mediated_escrow_deposit", id].
25
+ MEDIATED_ESCROW_DEPOSIT_SEED = 'mediated_escrow_deposit'
26
+
27
+ # PDA seed prefix for the per-mint program vault token account, derived from
28
+ # ["vault", mint]. A single vault is shared across all deposits for a mint.
29
+ VAULT_SEED = 'vault'
30
+
31
+ # Maps a cluster symbol/string to its program ID.
32
+ #
33
+ # @param cluster [Symbol, String] One of :mainnet, :devnet, :testnet, :localnet.
34
+ # @return [String] The base58 program ID for the cluster.
35
+ # @raise [ArgumentError] If the cluster is unknown.
36
+ def self.program_id_for(cluster)
37
+ case cluster.to_sym
38
+ when :mainnet then MAINNET_PROGRAM_ID
39
+ when :devnet then DEVNET_PROGRAM_ID
40
+ when :testnet then TESTNET_PROGRAM_ID
41
+ when :localnet then LOCALNET_PROGRAM_ID
42
+ else
43
+ raise ArgumentError, "Unknown cluster: #{cluster.inspect} (use :mainnet, :devnet, :testnet, :localnet)"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module ZarTrustlessEscrow
5
+ module Errors
6
+ # A resolved on-chain TrustlessEscrowError, carrying its custom error code,
7
+ # variant name, and message. Returned by {Errors.from_code} and
8
+ # {Errors.from_name} so callers can translate a failed transaction's custom
9
+ # error code into a meaningful, rescuable Ruby error.
10
+ class ProgramError < Solace::ZarTrustlessEscrow::Error
11
+ # @!attribute [r] code
12
+ # @return [Integer] The on-chain custom error code (6000-based).
13
+ attr_reader :code
14
+
15
+ # @!attribute [r] error_name
16
+ # @return [String] The TrustlessEscrowError variant name.
17
+ attr_reader :error_name
18
+
19
+ # @param code [Integer] The on-chain custom error code.
20
+ # @param error_name [String] The TrustlessEscrowError variant name.
21
+ # @param message [String] The variant's on-chain #[msg] text.
22
+ def initialize(code:, error_name:, message:)
23
+ @code = code
24
+ @error_name = error_name
25
+ super(message)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'errors/program_error'
4
+
5
+ module Solace
6
+ module ZarTrustlessEscrow
7
+ # Lookup for the on-chain TrustlessEscrowError codes, translating a custom
8
+ # program error code (e.g. from a failed transaction) into a {ProgramError}.
9
+ #
10
+ # The mapping is ported from the program's source of truth,
11
+ # programs/trustless_escrow/src/errors.rs — NOT the TypeScript SDK's errors
12
+ # file, which is stale (it omits DepositAmountInvalid, shifting every later
13
+ # code, and is missing all mediated-escrow errors). Anchor assigns each variant
14
+ # a code of {FIRST_ERROR_CODE} + its declaration index.
15
+ #
16
+ # @example
17
+ # Solace::ZarTrustlessEscrow::Errors.from_code(6019)
18
+ # # => #<ProgramError code=6019 error_name="MediatedExpiryMustBeInFuture" ...>
19
+ module Errors
20
+ extend self
21
+
22
+ # First on-chain custom error code (Anchor's default offset); maps to the
23
+ # first TrustlessEscrowError variant.
24
+ FIRST_ERROR_CODE = 6000
25
+
26
+ # TrustlessEscrowError variants in declaration order — index N is code
27
+ # FIRST_ERROR_CODE + N. Each entry is [variant name, on-chain #[msg] text].
28
+ DEFINITIONS = [
29
+ ['MintInvalid',
30
+ "MintInvalid: The mint provided does not match the escrow deposit's mint."],
31
+ ['DepositorInvalid',
32
+ 'Depositor invalid'],
33
+ ['DepositAmountInvalid',
34
+ 'DepositAmountInvalid: The deposit amount must be greater than 0.'],
35
+ ['SubsidyMismatch',
36
+ 'Subsidy mismatch'],
37
+ ['InvalidAuthority',
38
+ 'Either fee payer should be sponsor or depositor should be signer'],
39
+ ['InvalidCloseAuthority',
40
+ 'InvalidCloseAuthority: When a sponsor is present, the fee payer must be the sponsor.'],
41
+ ['ClaimAuthorityMustBeASigner',
42
+ 'ClaimAuthorityMustBeASigner: The claim authority must be a signer.'],
43
+ ['ClaimAuthorityIsInvalid',
44
+ "ClaimAuthorityIsInvalid: The claim authority provided does not match the escrow deposit's claim authority."],
45
+ ['DepositorCannotBeClaimant',
46
+ 'DepositorCannotBeClaimant: The depositor cannot be the claimant (use reclaim instruction instead).'],
47
+ ['ReclaimerMustBeDepositor',
48
+ 'ReclaimerMustBeDepositor: The reclaimer must be the depositor.'],
49
+ ['InvalidReclaimSigner',
50
+ 'InvalidReclaimSigner: The reclaimer signer must be the depositor or the sponsor.'],
51
+ ['MediatorCannotBeDepositorOrBeneficiary',
52
+ 'MediatorCannotBeDepositorOrBeneficiary: The mediator cannot be the depositor or the beneficiary.'],
53
+ ['InvalidMediator',
54
+ 'InvalidMediator: The signer is not the mediator of the mediated escrow deposit.'],
55
+ ['MediatedDepositorCannotBeBeneficiary',
56
+ 'MediatedDepositorCannotBeBeneficiary: The depositor and beneficiary cannot be the same account.'],
57
+ ['InvalidReleaseRecipient',
58
+ 'InvalidReleaseRecipient: The recipient must be either the depositor or the beneficiary.'],
59
+ ['MediatedReclaimNotExpirable',
60
+ 'MediatedReclaimNotExpirable: No expiry is set, so only the mediator can distribute the funds.'],
61
+ ['MediatedReclaimNotYetExpired',
62
+ 'MediatedReclaimNotYetExpired: The mediated escrow deposit cannot be reclaimed before its expiry timestamp.'],
63
+ ['MediatedReclaimerMustBeDepositor',
64
+ 'MediatedReclaimerMustBeDepositor: Only the depositor can reclaim the deposit after the expiry date.'],
65
+ ['InvalidRentCollector',
66
+ 'InvalidRentCollector: The rent collector provided does not match the mediated escrow ' \
67
+ "deposit's rent collector."],
68
+ ['MediatedExpiryMustBeInFuture',
69
+ 'MediatedExpiryMustBeInFuture: The expiry timestamp, when provided, must be in the future.']
70
+ ].freeze
71
+
72
+ # code => [name, message], derived by offsetting each declaration index with
73
+ # FIRST_ERROR_CODE.
74
+ BY_CODE = DEFINITIONS.each_with_index.to_h do |(name, message), index|
75
+ [FIRST_ERROR_CODE + index, [name, message]]
76
+ end.freeze
77
+
78
+ # name => code, for resolving an error by its variant name.
79
+ CODE_BY_NAME = BY_CODE.to_h { |code, (name, _message)| [name, code] }.freeze
80
+
81
+ # Resolves a {ProgramError} from an on-chain custom error code.
82
+ #
83
+ # @param code [Integer] The custom program error code (e.g. 6019).
84
+ # @return [ProgramError, nil] The typed error, or nil if the code is unknown.
85
+ def from_code(code)
86
+ name, message = BY_CODE[code]
87
+ return nil unless name
88
+
89
+ ProgramError.new(code:, error_name: name, message:)
90
+ end
91
+
92
+ # Resolves a {ProgramError} from a TrustlessEscrowError variant name.
93
+ #
94
+ # @param name [String] The variant name (e.g. "InvalidMediator").
95
+ # @return [ProgramError, nil] The typed error, or nil if the name is unknown.
96
+ def from_name(name)
97
+ code = CODE_BY_NAME[name]
98
+ return nil unless code
99
+
100
+ from_code(code)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module ZarTrustlessEscrow
5
+ module Instructions
6
+ # Encoder for the `claim` instruction: the claim authority releases an
7
+ # EscrowDeposit to the claimant and closes the deposit account.
8
+ class ClaimInstruction
9
+ # 8-byte Anchor discriminator: SHA256("global:claim")[0..7].
10
+ DISCRIMINATOR = [62, 198, 214, 193, 213, 159, 108, 210].freeze
11
+
12
+ # Builds a Solace::Instruction for `claim`.
13
+ #
14
+ # Account indices are in the on-chain order:
15
+ # mint, claim_authority, claimant, claimant_token_account,
16
+ # escrow_deposit, program_token_account, fee_payer, system_program,
17
+ # token_program, associated_token_program.
18
+ #
19
+ # @return [Solace::Instruction]
20
+ def self.build(
21
+ mint_index:,
22
+ claim_authority_index:,
23
+ claimant_index:,
24
+ claimant_token_account_index:,
25
+ escrow_deposit_index:,
26
+ program_token_account_index:,
27
+ fee_payer_index:,
28
+ system_program_index:,
29
+ token_program_index:,
30
+ associated_token_program_index:,
31
+ program_index:
32
+ )
33
+ Solace::Instruction.new.tap do |ix|
34
+ ix.program_index = program_index
35
+ ix.accounts = [
36
+ mint_index,
37
+ claim_authority_index,
38
+ claimant_index,
39
+ claimant_token_account_index,
40
+ escrow_deposit_index,
41
+ program_token_account_index,
42
+ fee_payer_index,
43
+ system_program_index,
44
+ token_program_index,
45
+ associated_token_program_index
46
+ ]
47
+ ix.data = data
48
+ end
49
+ end
50
+
51
+ # Encodes the `claim` instruction data (discriminator only — no args).
52
+ #
53
+ # @return [Array<Integer>]
54
+ def self.data
55
+ DISCRIMINATOR.dup
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module ZarTrustlessEscrow
5
+ module Instructions
6
+ # Encoder for the `deposit` instruction: locks `amount` tokens from the
7
+ # depositor into a per-claim-authority EscrowDeposit PDA and the per-mint
8
+ # program vault.
9
+ class DepositInstruction
10
+ # 8-byte Anchor discriminator: SHA256("global:deposit")[0..7].
11
+ DISCRIMINATOR = [242, 35, 198, 137, 82, 225, 242, 182].freeze
12
+
13
+ # Builds a Solace::Instruction for `deposit`.
14
+ #
15
+ # Account indices are in the on-chain order:
16
+ # mint, depositor, depositor_token_account, escrow_deposit,
17
+ # program_token_account, fee_payer, system_program, token_program.
18
+ #
19
+ # @return [Solace::Instruction]
20
+ def self.build(
21
+ amount:,
22
+ claim_authority:,
23
+ sponsor:,
24
+ mint_index:,
25
+ depositor_index:,
26
+ depositor_token_account_index:,
27
+ escrow_deposit_index:,
28
+ program_token_account_index:,
29
+ fee_payer_index:,
30
+ system_program_index:,
31
+ token_program_index:,
32
+ program_index:
33
+ )
34
+ Solace::Instruction.new.tap do |ix|
35
+ ix.program_index = program_index
36
+ ix.accounts = [
37
+ mint_index,
38
+ depositor_index,
39
+ depositor_token_account_index,
40
+ escrow_deposit_index,
41
+ program_token_account_index,
42
+ fee_payer_index,
43
+ system_program_index,
44
+ token_program_index
45
+ ]
46
+ ix.data = data(amount:, claim_authority:, sponsor:)
47
+ end
48
+ end
49
+
50
+ # Encodes the DepositCreateArgs struct in Borsh format.
51
+ #
52
+ # @param amount [Integer] u64 amount to deposit.
53
+ # @param claim_authority [#to_s] base58 authority that can claim the deposit.
54
+ # @param sponsor [#to_s, nil] optional base58 fee sponsor.
55
+ # @return [Array<Integer>]
56
+ def self.data(amount:, claim_authority:, sponsor:)
57
+ DISCRIMINATOR +
58
+ Solace::Utils::Codecs.encode_le_u64(amount).bytes +
59
+ Solace::Utils::Codecs.encode_pubkey(claim_authority) +
60
+ Solace::Utils::Codecs.encode_option_pubkey(sponsor)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end