Skip to content

Commit a02b87e

Browse files
BRBussyclaude
andauthored
feat(wallet/account): add RegisterTokensToAccount and DeregisterTokensFromAccount methods (#150)
Add two new RPC methods to AccountService for managing token registration on accounts: - RegisterTokensToAccount: Configure account to receive and hold specified tokens - DeregisterTokensFromAccount: Remove token support from account (requires zero balance) Both methods return a ledger transaction reference for monitoring the operation. Includes generated SDK code (Go, Python) and documentation with examples. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e7fab72 commit a02b87e

14 files changed

Lines changed: 990 additions & 59 deletions

File tree

docs/docs/api-reference/wallet/account/v1/index.mdx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,18 @@ Accounts serve as the primary containers for holding and managing digital assets
3232

3333
1. **Create Account** - Register account in system (off-chain)
3434
2. **Open Account** - Initialize on blockchain (on-chain)
35-
3. **Use Account** - Receive deposits, execute trades
36-
4. **Query Balances** - Monitor holdings with live data
37-
5. **Close Account** - Deactivate when no longer needed
35+
3. **Register Tokens** - Configure account to hold specific assets
36+
4. **Use Account** - Receive deposits, execute trades
37+
5. **Query Balances** - Monitor holdings with live data
38+
6. **Deregister Tokens** - Remove asset support (requires zero balance)
39+
7. **Close Account** - Deactivate when no longer needed
40+
41+
### Token Registration
42+
43+
Before an account can hold a specific token (asset), the token must be registered on the account. This is a ledger-level operation that configures the account to receive and hold the token.
44+
45+
- **RegisterTokensToAccount** - Configure account to hold specified tokens (1-10 tokens per request)
46+
- **DeregisterTokensFromAccount** - Remove token support from account (requires zero balance)
3847

3948
## Key Concepts
4049

@@ -80,9 +89,12 @@ The AccountService uses role-based access control:
8089
### Write Operations
8190
Require `ROLE_WALLET_ADMIN` or `ROLE_WALLET_ACCOUNT_ADMIN`:
8291
- CreateAccount
83-
- UpdateAccount
92+
- UpdateAccount
8493
- OpenAccount
85-
- CloseAccount
94+
- AddSignatoriesToAccount
95+
- RemoveSignatoriesFromAccount
96+
- RegisterTokensToAccount
97+
- DeregisterTokensFromAccount
8698

8799
### Read Operations
88100
Require any wallet role (`ADMIN` or `VIEWER` variants):
@@ -98,6 +110,8 @@ You can only operate on accounts in your executing group.
98110

99111
1. **Account Creation**: Always specify a descriptive `display_name` for easy identification
100112
2. **Ledger Selection**: Choose the appropriate ledger based on your trading needs
101-
3. **Balance Queries**: Use `populate_ledger_data=false` for listing, `true` for trading decisions
102-
4. **Error Handling**: Check account state before operations - closed accounts cannot transact
103-
5. **Monitoring**: Track the `ledger_transaction` reference when opening/closing accounts
113+
3. **Token Registration**: Register tokens before attempting to receive deposits of that asset
114+
4. **Balance Queries**: Use `populate_ledger_data=false` for listing, `true` for trading decisions
115+
5. **Error Handling**: Check account state before operations - closed accounts cannot transact
116+
6. **Token Deregistration**: Ensure token balance is zero before deregistering
117+
7. **Monitoring**: Track the `ledger_transaction` reference when performing ledger operations
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"io"
6+
"log"
7+
8+
transaction_v1 "github.com/meshtrade/api/go/ledger/transaction/v1"
9+
type_v1 "github.com/meshtrade/api/go/type/v1"
10+
account_v1 "github.com/meshtrade/api/go/wallet/account/v1"
11+
)
12+
13+
func main() {
14+
ctx := context.Background()
15+
16+
// Default configuration is used and credentials come from MESH_API_CREDENTIALS
17+
// environment variable or default discovery methods. Zero config required
18+
// unless you want custom configuration.
19+
accountService, err := account_v1.NewAccountService()
20+
if err != nil {
21+
log.Fatalf("Failed to create account service: %v", err)
22+
}
23+
defer accountService.Close()
24+
transactionService, err := transaction_v1.NewTransactionService()
25+
if err != nil {
26+
log.Fatalf("Failed to create transaction service: %v", err)
27+
}
28+
defer transactionService.Close()
29+
30+
// Call the DeregisterTokensFromAccount method
31+
// IMPORTANT: Token balances must be zero before deregistration will succeed
32+
// You can deregister 1-10 tokens in a single request
33+
response, err := accountService.DeregisterTokensFromAccount(
34+
ctx,
35+
&account_v1.DeregisterTokensFromAccountRequest{
36+
// Resource name of account to deregister tokens from
37+
Name: "accounts/01HQ3K5M8XYZ2NFVJT9BKR7P4C",
38+
// Tokens to deregister from the account (supports 1-10 tokens)
39+
Tokens: []*type_v1.Token{
40+
{
41+
Code: "USDC",
42+
Issuer: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
43+
Ledger: type_v1.Ledger_LEDGER_STELLAR,
44+
},
45+
{
46+
Code: "EURC",
47+
Issuer: "GDHU6WRG4IEQXM5NZ4BMPKOXHW76MZM4Y2IEMFDVXBSDP6SJY4ITNPP2",
48+
Ledger: type_v1.Ledger_LEDGER_STELLAR,
49+
},
50+
},
51+
},
52+
)
53+
if err != nil {
54+
log.Fatalf("DeregisterTokensFromAccount failed: %v", err)
55+
}
56+
log.Printf(
57+
"DeregisterTokensFromAccount completed successfully with ledger transaction %s submitted",
58+
response.GetLedgerTransaction(),
59+
)
60+
61+
// get a stream to monitor the state of the DeregisterTokensFromAccount transaction
62+
stream, err := transactionService.MonitorTransactionState(
63+
ctx,
64+
&transaction_v1.MonitorTransactionStateRequest{
65+
Name: response.GetLedgerTransaction(),
66+
},
67+
)
68+
if err != nil {
69+
log.Fatalf("MonitorTransactionState failed: %v", err)
70+
}
71+
log.Printf("Stream opened to monitor DeregisterTokensFromAccount transaction state")
72+
73+
// read from the stream until completion
74+
monitorTransaction:
75+
for {
76+
transactionResponse, err := stream.Recv()
77+
if err == io.EOF {
78+
break // Stream completed normally
79+
}
80+
if err != nil {
81+
// Other errors:
82+
// - timeout of ctx passed to MonitorTransactionState
83+
// - arbitrary network errors
84+
// - other arbitrary errors
85+
log.Fatalf("MonitorTransactionState failed: %v", err)
86+
}
87+
88+
// Process each response as it arrives
89+
log.Printf("Received: %+v", transactionResponse.GetState())
90+
91+
// Check for transaction end state
92+
switch transactionResponse.GetState() {
93+
case transaction_v1.TransactionState_TRANSACTION_STATE_SIGNING_IN_PROGRESS,
94+
transaction_v1.TransactionState_TRANSACTION_STATE_SUBMISSION_IN_PROGRESS,
95+
transaction_v1.TransactionState_TRANSACTION_STATE_INDETERMINATE:
96+
log.Printf("DeregisterTokensFromAccount transaction in state %s, keep waiting...", transactionResponse.GetState())
97+
98+
case transaction_v1.TransactionState_TRANSACTION_STATE_SUCCESSFUL:
99+
log.Printf("DeregisterTokensFromAccount transaction successful - tokens deregistered from account")
100+
break monitorTransaction
101+
102+
case transaction_v1.TransactionState_TRANSACTION_STATE_FAILED:
103+
log.Printf("DeregisterTokensFromAccount transaction failed")
104+
break monitorTransaction
105+
106+
default:
107+
log.Fatalf("Received unexpected transaction state: %v", transactionResponse.GetState())
108+
}
109+
}
110+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import co.meshtrade.api.type.v1.Type.Ledger;
2+
import co.meshtrade.api.type.v1.Type.Token;
3+
import co.meshtrade.api.wallet.account.v1.AccountService;
4+
import co.meshtrade.api.wallet.account.v1.Service.DeregisterTokensFromAccountRequest;
5+
import co.meshtrade.api.wallet.account.v1.Service.DeregisterTokensFromAccountResponse;
6+
7+
import java.util.Optional;
8+
9+
public class DeregisterTokensFromAccountExample {
10+
public static void main(String[] args) {
11+
// Default configuration is used and credentials come from MESH_API_CREDENTIALS
12+
// environment variable or default discovery methods. Zero config required
13+
// unless you want custom configuration.
14+
try (AccountService service = new AccountService()) {
15+
// Create request with tokens to deregister
16+
// IMPORTANT: Token balances must be zero before deregistration will succeed
17+
// You can deregister 1-10 tokens in a single request
18+
DeregisterTokensFromAccountRequest request = DeregisterTokensFromAccountRequest.newBuilder()
19+
// Resource name of account to deregister tokens from
20+
.setName("accounts/01HQ3K5M8XYZ2NFVJT9BKR7P4C")
21+
// Add tokens to deregister from the account
22+
.addTokens(Token.newBuilder()
23+
.setCode("USDC")
24+
.setIssuer("GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN")
25+
.setLedger(Ledger.LEDGER_STELLAR)
26+
.build())
27+
.addTokens(Token.newBuilder()
28+
.setCode("EURC")
29+
.setIssuer("GDHU6WRG4IEQXM5NZ4BMPKOXHW76MZM4Y2IEMFDVXBSDP6SJY4ITNPP2")
30+
.setLedger(Ledger.LEDGER_STELLAR)
31+
.build())
32+
.build();
33+
34+
// Call the DeregisterTokensFromAccount method
35+
DeregisterTokensFromAccountResponse response = service.deregisterTokensFromAccount(request, Optional.empty());
36+
37+
// The response contains a ledger transaction reference to monitor
38+
System.out.println("DeregisterTokensFromAccount submitted: " + response.getLedgerTransaction());
39+
System.out.println("Monitor the transaction state to confirm completion");
40+
} catch (Exception e) {
41+
System.err.println("DeregisterTokensFromAccount failed: " + e.getMessage());
42+
e.printStackTrace();
43+
}
44+
}
45+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from meshtrade.type.v1 import Ledger, Token
2+
from meshtrade.wallet.account.v1 import (
3+
AccountService,
4+
DeregisterTokensFromAccountRequest,
5+
)
6+
7+
8+
def main():
9+
# Default configuration is used and credentials come from MESH_API_CREDENTIALS
10+
# environment variable or default discovery methods. Zero config required
11+
# unless you want custom configuration.
12+
service = AccountService()
13+
14+
with service:
15+
# Create request with tokens to deregister
16+
# IMPORTANT: Token balances must be zero before deregistration will succeed
17+
# You can deregister 1-10 tokens in a single request
18+
request = DeregisterTokensFromAccountRequest(
19+
# Resource name of account to deregister tokens from
20+
name="accounts/01HQ3K5M8XYZ2NFVJT9BKR7P4C",
21+
# Tokens to deregister from the account
22+
tokens=[
23+
Token(
24+
code="USDC",
25+
issuer="GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
26+
ledger=Ledger.LEDGER_STELLAR,
27+
),
28+
Token(
29+
code="EURC",
30+
issuer="GDHU6WRG4IEQXM5NZ4BMPKOXHW76MZM4Y2IEMFDVXBSDP6SJY4ITNPP2",
31+
ledger=Ledger.LEDGER_STELLAR,
32+
),
33+
],
34+
)
35+
36+
# Call the DeregisterTokensFromAccount method
37+
response = service.deregister_tokens_from_account(request)
38+
39+
# The response contains a ledger transaction reference to monitor
40+
print(f"DeregisterTokensFromAccount submitted: {response.ledger_transaction}")
41+
print("Monitor the transaction state to confirm completion")
42+
43+
44+
if __name__ == "__main__":
45+
main()
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"io"
6+
"log"
7+
8+
transaction_v1 "github.com/meshtrade/api/go/ledger/transaction/v1"
9+
type_v1 "github.com/meshtrade/api/go/type/v1"
10+
account_v1 "github.com/meshtrade/api/go/wallet/account/v1"
11+
)
12+
13+
func main() {
14+
ctx := context.Background()
15+
16+
// Default configuration is used and credentials come from MESH_API_CREDENTIALS
17+
// environment variable or default discovery methods. Zero config required
18+
// unless you want custom configuration.
19+
accountService, err := account_v1.NewAccountService()
20+
if err != nil {
21+
log.Fatalf("Failed to create account service: %v", err)
22+
}
23+
defer accountService.Close()
24+
transactionService, err := transaction_v1.NewTransactionService()
25+
if err != nil {
26+
log.Fatalf("Failed to create transaction service: %v", err)
27+
}
28+
defer transactionService.Close()
29+
30+
// Call the RegisterTokensToAccount method
31+
// You can register 1-10 tokens in a single request
32+
response, err := accountService.RegisterTokensToAccount(
33+
ctx,
34+
&account_v1.RegisterTokensToAccountRequest{
35+
// Resource name of account to register tokens on
36+
Name: "accounts/01HQ3K5M8XYZ2NFVJT9BKR7P4C",
37+
// Tokens to register on the account (supports 1-10 tokens)
38+
Tokens: []*type_v1.Token{
39+
{
40+
Code: "USDC",
41+
Issuer: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
42+
Ledger: type_v1.Ledger_LEDGER_STELLAR,
43+
},
44+
{
45+
Code: "EURC",
46+
Issuer: "GDHU6WRG4IEQXM5NZ4BMPKOXHW76MZM4Y2IEMFDVXBSDP6SJY4ITNPP2",
47+
Ledger: type_v1.Ledger_LEDGER_STELLAR,
48+
},
49+
},
50+
},
51+
)
52+
if err != nil {
53+
log.Fatalf("RegisterTokensToAccount failed: %v", err)
54+
}
55+
log.Printf(
56+
"RegisterTokensToAccount completed successfully with ledger transaction %s submitted",
57+
response.GetLedgerTransaction(),
58+
)
59+
60+
// get a stream to monitor the state of the RegisterTokensToAccount transaction
61+
stream, err := transactionService.MonitorTransactionState(
62+
ctx,
63+
&transaction_v1.MonitorTransactionStateRequest{
64+
Name: response.GetLedgerTransaction(),
65+
},
66+
)
67+
if err != nil {
68+
log.Fatalf("MonitorTransactionState failed: %v", err)
69+
}
70+
log.Printf("Stream opened to monitor RegisterTokensToAccount transaction state")
71+
72+
// read from the stream until completion
73+
monitorTransaction:
74+
for {
75+
transactionResponse, err := stream.Recv()
76+
if err == io.EOF {
77+
break // Stream completed normally
78+
}
79+
if err != nil {
80+
// Other errors:
81+
// - timeout of ctx passed to MonitorTransactionState
82+
// - arbitrary network errors
83+
// - other arbitrary errors
84+
log.Fatalf("MonitorTransactionState failed: %v", err)
85+
}
86+
87+
// Process each response as it arrives
88+
log.Printf("Received: %+v", transactionResponse.GetState())
89+
90+
// Check for transaction end state
91+
switch transactionResponse.GetState() {
92+
case transaction_v1.TransactionState_TRANSACTION_STATE_SIGNING_IN_PROGRESS,
93+
transaction_v1.TransactionState_TRANSACTION_STATE_SUBMISSION_IN_PROGRESS,
94+
transaction_v1.TransactionState_TRANSACTION_STATE_INDETERMINATE:
95+
log.Printf("RegisterTokensToAccount transaction in state %s, keep waiting...", transactionResponse.GetState())
96+
97+
case transaction_v1.TransactionState_TRANSACTION_STATE_SUCCESSFUL:
98+
log.Printf("RegisterTokensToAccount transaction successful - tokens registered on account")
99+
break monitorTransaction
100+
101+
case transaction_v1.TransactionState_TRANSACTION_STATE_FAILED:
102+
log.Printf("RegisterTokensToAccount transaction failed")
103+
break monitorTransaction
104+
105+
default:
106+
log.Fatalf("Received unexpected transaction state: %v", transactionResponse.GetState())
107+
}
108+
}
109+
}

0 commit comments

Comments
 (0)